Back to posts
849 words
3 min read

Running Some Python

tech elixir python live_view

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