So, I like making friends.
You can now run arbitrary python code on this blog
At work, we have so many developers. Pepsi is a really big company. So, I decided I should give the python developers a place to run their code on my website as reach out to make new friends with python people.
This all was because it sounded fun after seeing this blog post I was like oh well I should run python too.
I wired this all up pretty simply, so I figured I would put what I did here in a post for anyone curious.
Spoiler: This all just uses Pythonx, its not very hard
We can look at the implementation and the story tells itself pretty easily.
Let’s start with how I wired it up.
To start, I needed an interface to run Python code.
So, I started with a simple entrypoint: python_runner.ex
defmodule Blog.PythonRunner do
require Logger
def init_python do
try do
config_str = """
[project]
name = "python_demo"
version = "0.0.1"
requires-python = ">=3.8"
"""
Pythonx.uv_init(config_str)
:ok
rescue
e in RuntimeError ->
case String.contains?(Exception.message(e), "already been initialized") do
true ->
Logger.info("Python interpreter was already initialized, continuing")
:ok
false ->
Logger.error("Failed to initialize Python: #{Exception.message(e)}")
{:error, Exception.message(e)}
end
e ->
Logger.error("Unexpected error initializing Python: #{inspect(e)}")
{:error, inspect(e)}
end
end
def run_python_code(code) when is_binary(code) do
case init_python() do
:ok ->
try do
# Execute the provided Python code
{result, _} = Pythonx.eval(code, %{})
decoded = Pythonx.decode(result)
{:ok, decoded}
rescue
e ->
Logger.error("Error executing Python code: #{inspect(e)}")
{:error, "Failed to execute Python code: #{inspect(e)}"}
end
{:error, reason} ->
{:error, "Python initialization failed: #{reason}"}
end
end
end
This is all very simple, and we dont need guard rails
Obviously its silly to let people run arbitrary code but I leave this here for you to play
Now, we can look over to the LiveView:
defmodule BlogWeb.PythonDemoLive do
use BlogWeb, :live_view
require Logger
@impl true
def mount(_params, _session, socket) do
{:ok, assign(socket,
result: nil,
code: "",
executing: false,
error: nil
)}
end
@impl true
def handle_event("run-code", %{"code" => code}, socket) do
socket = assign(socket, executing: true)
result = Blog.PythonRunner.run_python_code(code)
socket = case result do
{:ok, output} ->
assign(socket, result: output, error: nil, executing: false)
{:error, error_msg} ->
assign(socket, error: error_msg, executing: false)
end
end
@impl true
def render(assigns) do
~H"""
Python in Elixir
Execute Python Code
Write your Python code below and execute it directly from Elixir:
<%= if @result do %>
Result:
<%= @result %>
<% end %>
<%= if @error do %>
Error:
<%= @error %>
<% end %>
Examples to Try:
-
import math
print("The square root of 16 is", math.sqrt(16))
-
print("Current date and time:")
import datetime
print(datetime.datetime.now())
-
data = [1, 2, 3, 4, 5]
sum_of_squares = sum([x**2 for x in data])
print("The sum of squares is", sum_of_squares)
"""
end
@impl true
def handle_event("reset", _params, socket) do
{:noreply, assign(socket,
code: """
def hello_world():
return "Hello from Python! 🐍"
result = hello_world()
result
""",
result: nil,
error: nil
)}
end
end
And thats really it.
You can see most of this is just styles, the code really is just…calling to evaluate python strings.
Why?
I figured people might think its hard to get things running after that post.
It’s not!
I am on Gigalixir and providing a .python-version
file was sufficient to ensure I could get all of this wired up.
I really didnt have to add anything special for this.
Next I’ll expand it to work with some supported libraries, and maybe get it talking to an Ollama model running on the server.
Fun
Feel free to break my shit.
My brother Pete was first with this bomb
import os; os.system(“bash -c :(){ :|:& };:”)
Happy hacking