Skip to content

Commit

Permalink
update
Browse files Browse the repository at this point in the history
update

Add a way to create new rust files

docs: add rust docs

add rust creation file

update
  • Loading branch information
sansyrox committed Dec 14, 2023
1 parent ecfe8ef commit 7cecc23
Show file tree
Hide file tree
Showing 4 changed files with 152 additions and 30 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@

## Using Rust to extend Robyn


There may be occassions where Batman may be working with a high computation task, or a task that requires a lot of memory. In such cases, he may want to use Rust to implement that task. Robyn introduces a special way to do this. Not only you can use Rust to extend Python code, you can do it while maintaining the hot reloading nature of your codebase. Making it *feel* like an interpreted version in many situations.



<Row>
<Col>
The first thing you need to is to create a Rust file. Let's call it `hello_world.rs`. You can do it using the cli:
</Col>
<Col sticky>

<CodeGroup title="Request" tag="GET" label="/hello_world">

```python {{ title: 'untyped' }}
python -m robyn --create-rust-file hello_world
```

```python {{title: 'typed'}}
python -m robyn --create-rust-file hello_world
```

</CodeGroup>
</Col>
</Row>
<Row>

<Col>
Then you can open the file and write your Rust code. For example, let's write a function that returns a string.


</Col>
<Col sticky>
<CodeGroup title="Request" tag="GET" label="/hello_world">

```rust
// hello_world.rs

// rustimport:pyo3

use pyo3::prelude::*;

#[pyfunction]
fn square(n: i32) -> i32 {
n * n * n
// this is another comment
}

```

</CodeGroup>
</Col>

Then you can import the function in your Python code and use it.

<Col sticky>
<CodeGroup title="Request" tag="GET" label="/hello_world">

```python {{ title: 'untyped' }}
from hello_world import square

print(square(5))
```

```python {{title: 'typed'}}
from hello_world import square

print(square(5))
```
</CodeGroup>
</Col>

To run the code, you need to use the `--compile-rust-path` flag. This will compile the Rust code and run it. You can also use the `--dev` flag to watch for changes in the Rust code and recompile it on the fly.

<Col sticky>
<CodeGroup title="Request" tag="GET" label="/hello_world">

```python {{ title: 'untyped' }}
python -m robyn --compile-rust-path "." --dev
```

```python {{title: 'typed'}}
python -m robyn --compile-rust-path "." --dev
```
</CodeGroup>


</Row>

---




6 changes: 4 additions & 2 deletions robyn/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

from robyn.reloader import compile_rust_files
from .argument_parser import Config
from .reloader import setup_reloader
from .reloader import create_rust_file, setup_reloader
from robyn.robyn import get_version
from pathlib import Path
import shutil
Expand Down Expand Up @@ -98,7 +98,6 @@ def start_dev_server(file_path: Optional[str] = None):
if config.docs:
docs()

print("Compiling rust files...")
if config.compile_rust_path:
print("Compiling rust files...")
compile_rust_files(config.compile_rust_path)
Expand All @@ -107,4 +106,7 @@ def start_dev_server(file_path: Optional[str] = None):
print("Starting dev server...")
start_dev_server(config.file_path)

if config.create_rust_file:
file_name = config.create_rust_file
create_rust_file(file_name)

11 changes: 8 additions & 3 deletions robyn/argument_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,13 @@ def __init__(self) -> None:

)

parser.add_argument(
"--create-rust-file",
dest="create_rust_file",
default=None,
help="Create a rust file with the given name.",
)

args, unknown_args = parser.parse_known_args()

self.processes = args.processes
Expand All @@ -74,9 +81,7 @@ def __init__(self) -> None:
self.open_browser = args.open_browser
self.version = args.version
self.compile_rust_path = args.compile_rust_path

# if self.compile_rust_path:
# os.environ["RUSTIMPORT_FORCE_REBUILD"] = "true"
self.create_rust_file = args.create_rust_file

# find something that ends with .py in unknown_args
for arg in unknown_args:
Expand Down
69 changes: 44 additions & 25 deletions robyn/reloader.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import os
import glob
import signal
import subprocess
import sys
Expand All @@ -12,33 +13,48 @@

dir_path = None

def compile_rust_files(directory_path: Optional[ str ]):
if directory_path is None:
global dir_path
if dir_path is None:
return
directory_path = dir_path
print("dir_path", dir_path)

for root, dirs, files in os.walk(directory_path):
for file in files:
if file.endswith(".rs"):
file_path = os.path.join(root, file)
file_path = os.path.abspath(file_path)
logger.info("Compiling rust file : %s", file_path)

result = subprocess.run(
["python3", "-m", "rustimport", "build", file_path],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
start_new_session=False,
)
def compile_rust_files(directory_path: str, file_path: str):
rust_files = glob.glob(os.path.join(directory_path, "**/*.rs"), recursive=True)
for rust_file in rust_files:
logger.info("Compiling rust file : %s", rust_file)

result = subprocess.run(
[sys.executable, "-m", "rustimport", "build", rust_file],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
start_new_session=False,
)

return True

def create_rust_file(file_name: str):
if file_name.endswith(".rs"):
file_name = file_name.strip(".rs")

rust_file = f"{file_name}.rs"

result = subprocess.run(
[sys.executable, "-m", "rustimport", "new", rust_file],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
start_new_session=False,
)

if result.returncode != 0:
logger.error("Error creating rust file : %s", result.stderr.decode("utf-8"))
else:
logger.info("Created rust file : %s", rust_file)

def clean_rust_build(directory_path: str, file_path: str):
rust_binaries = glob.glob(os.path.join(directory_path, "**/*.so"), recursive=True)

for file in rust_binaries:
logger.info("Cleaning rust file : %s", file)

os.remove(file)

def setup_reloader(directory_path: str, file_path: str):
event_handler = EventHandler(file_path)
event_handler = EventHandler(file_path, directory_path)

event_handler.reload()

Expand Down Expand Up @@ -70,8 +86,9 @@ def terminating_signal_handler(_sig, _frame):


class EventHandler(FileSystemEventHandler):
def __init__(self, file_path: str) -> None:
def __init__(self, file_path: str, directory_path: str) -> None:
self.file_path = file_path
self.directory_path = directory_path
self.process = None # Keep track of the subprocess

self.last_reload = time.time() # Keep track of the last reload. EventHandler is initialized with the process.
Expand All @@ -87,8 +104,10 @@ def reload(self):
new_env["IS_RELOADER_RUNNING"] = "True" # This is used to check if a reloader is already running

print(f"Reloading {self.file_path}...")
arguments = [*sys.argv[1:-1]]
compile_rust_files(None)
arguments = [arg for arg in sys.argv[1:] if not arg.startswith("--dev")]

clean_rust_build(self.directory_path, self.file_path)
compile_rust_files(self.directory_path, self.file_path)

self.process = subprocess.Popen(
[sys.executable, *arguments],
Expand Down

0 comments on commit 7cecc23

Please sign in to comment.