an attempt at python for the upstream, this works locally
Bobby Grayson 3 days ago 5 files (+187, -1)
@@ -0,0 +1,60 @@+defmodule Blog.PythonRunner do+ @moduledoc """+ Simple module for running Python code using Pythonx.+ """++ require Logger++ @doc """+ Initializes a basic Python environment with no additional packages.+ Safely handles the case where Python is already initialized.+ """+ def init do+ try do+ # Initialize Python with minimal configuration+ Pythonx.uv_init("""+ [project]+ name = "python_demo"+ version = "0.0.1"+ requires-python = ">=3.8"+ dependencies = []+ """)++ Logger.info("Python environment initialized successfully")+ :ok+ rescue+ e in RuntimeError ->+ if String.contains?(Exception.message(e), "already been initialized") do+ Logger.info("Python interpreter was already initialized, continuing")+ :ok+ else+ Logger.error("Failed to initialize Python: #{Exception.message(e)}")+ reraise e, __STACKTRACE__+ end+ end+ end++ @doc """+ Runs a basic hello world Python script and returns the result.+ """+ def hello_world do+ python_code = """+ def say_hello():+ return "Hello from Python! ๐"++ result = say_hello()+ result+ """++ {result, _} = Pythonx.eval(python_code, %{})+ Pythonx.decode(result)+ end++ @doc """+ Runs arbitrary Python code and returns the result.+ """+ def run_code(code) when is_binary(code) do+ {result, _} = Pythonx.eval(code, %{})+ Pythonx.decode(result)+ end+end
@@ -0,0 +1,120 @@+defmodule BlogWeb.PythonLive.Index do+ use BlogWeb, :live_view+ alias Blog.PythonRunner+ require Logger++ @python_example """+ import random++ # Roll some dice+ dice = [random.randint(1, 6) for _ in range(3)]+ f"You rolled: {dice} (sum: {sum(dice)})"+ """++ @impl true+ def mount(_params, _session, socket) do+ # Initialize the Python environment - now returns :ok instead of {:ok, _}+ python_status = case PythonRunner.init() do+ :ok -> :available+ {:error, reason} -> {:unavailable, reason}+ end++ # Get the hello world result+ hello_result = if python_status == :available do+ try do+ {result, _} = PythonRunner.hello_world()+ result+ rescue+ e ->+ Logger.error("Error running hello world: #{inspect(e)}")+ "Error: #{inspect(e)}"+ end+ else+ "Python unavailable"+ end++ {:ok, assign(socket,+ code: @python_example,+ result: "",+ hello_result: hello_result,+ python_status: python_status+ )}+ end++ @impl true+ def handle_event("run-code", %{"code" => code}, socket) do+ result = if socket.assigns.python_status == :available do+ try do+ PythonRunner.run_code(code)+ rescue+ e ->+ Logger.error("Error executing Python code: #{inspect(e)}")+ "Error: #{inspect(e)}"+ end+ else+ "Python is unavailable"+ end++ Logger.info("Python code execution result: #{inspect(result)}")+ {:noreply, assign(socket, result: result)}+ end++ @impl true+ def render(assigns) do+ ~H"""+ <div class="container mx-auto p-4">+ <h1 class="text-2xl font-bold mb-4">Python Integration Demo</h1>++ <div class="mb-6 p-4 rounded bg-gray-100">+ <h2 class="text-xl font-semibold mb-2">Python Status</h2>+ <%= if @python_status == :available do %>+ <div class="text-green-600 font-bold">โ Python is available</div>+ <% else %>+ <div class="text-red-600 font-bold">โ Python is unavailable</div>+ <div class="mt-2 p-2 bg-yellow-100 rounded">+ <p>Reason: <%= inspect(@python_status) %></p>+ </div>+ <% end %>+ </div>++ <div class="mb-6 p-4 rounded bg-gray-100">+ <h2 class="text-xl font-semibold mb-2">Hello from Python</h2>+ <div class="p-2 bg-white rounded border">+ <%= @hello_result %>+ </div>+ </div>++ <div class="mb-6">+ <h2 class="text-xl font-semibold mb-2">Try Python Code</h2>+ <form phx-submit="run-code">+ <div class="mb-4">+ <textarea+ name="code"+ rows="10"+ class="w-full p-2 border rounded font-mono"+ placeholder="Enter Python code here"+ disabled={@python_status != :available}+ ><%= @code %></textarea>+ </div>+ <button+ type="submit"+ class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"+ disabled={@python_status != :available}+ >+ Run Code+ </button>+ </form>+ </div>++ <%= if @result != "" do %>+ <div class="mt-4">+ <h3 class="text-lg font-semibold mb-2">Result</h3>+ <div class="p-4 bg-gray-100 rounded font-mono whitespace-pre-wrap">+ <%= @result %>+ </div>+ </div>+ <% end %>+ </div>+ """+ end+end
MODIFIED
lib/blog_web/router.ex
MODIFIED
lib/blog_web/router.ex
@@ -30,6 +30,7 @@ live "/cursor-tracker", CursorTrackerLive, :indexlive "/emoji-skeets", EmojiSkeetsLive, :indexlive "/allowed-chats", AllowedChatsLive, :indexlive "/hacker-news", HackerNewsLive, :index+ live "/python", PythonLive.Index, :indexend# Other scopes may use custom stacks.
MODIFIED
mix.exs
MODIFIED
mix.exs
@@ -52,7 +52,8 @@ {:gettext, "~> 0.26"},{:jason, "~> 1.2"},{:bandit, "~> 1.5"},{:earmark, "~> 1.4"},- {:websockex, "~> 0.4.3"}+ {:websockex, "~> 0.4.3"},+ {:pythonx, "~> 0.4.2"}]end
MODIFIED
mix.lock
MODIFIED
mix.lock
@@ -1,16 +1,19 @@%{"bandit": {:hex, :bandit, "1.6.7", "42f30e37a1c89a2a12943c5dca76f731a2313e8a2e21c1a95dc8241893e922d1", [:mix], [{:hpax, "~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "551ba8ff5e4fc908cbeb8c9f0697775fb6813a96d9de5f7fe02e34e76fd7d184"},"castore": {:hex, :castore, "1.0.11", "4bbd584741601eb658007339ea730b082cc61f3554cf2e8f39bf693a11b49073", [:mix], [], "hexpm", "e03990b4db988df56262852f20de0f659871c35154691427a5047f4967a16a62"},+ "cc_precompiler": {:hex, :cc_precompiler, "0.1.10", "47c9c08d8869cf09b41da36538f62bc1abd3e19e41701c2cea2675b53c704258", [:mix], [{:elixir_make, "~> 0.7", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "f6e046254e53cd6b41c6bacd70ae728011aa82b2742a80d6e2214855c6e06b22"},"db_connection": {:hex, :db_connection, "2.7.0", "b99faa9291bb09892c7da373bb82cba59aefa9b36300f6145c5f201c7adf48ec", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dcf08f31b2701f857dfc787fbad78223d61a32204f217f15e881dd93e4bdd3ff"},"decimal": {:hex, :decimal, "2.3.0", "3ad6255aa77b4a3c4f818171b12d237500e63525c2fd056699967a3e7ea20f62", [:mix], [], "hexpm", "a4d66355cb29cb47c3cf30e71329e58361cfcb37c34235ef3bf1d7bf3773aeac"},"dns_cluster": {:hex, :dns_cluster, "0.1.3", "0bc20a2c88ed6cc494f2964075c359f8c2d00e1bf25518a6a6c7fd277c9b0c66", [:mix], [], "hexpm", "46cb7c4a1b3e52c7ad4cbe33ca5079fbde4840dedeafca2baf77996c2da1bc33"},"earmark": {:hex, :earmark, "1.4.47", "7e7596b84fe4ebeb8751e14cbaeaf4d7a0237708f2ce43630cfd9065551f94ca", [:mix], [], "hexpm", "3e96bebea2c2d95f3b346a7ff22285bc68a99fbabdad9b655aa9c6be06c698f8"},"ecto": {:hex, :ecto, "3.12.5", "4a312960ce612e17337e7cefcf9be45b95a3be6b36b6f94dfb3d8c361d631866", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "6eb18e80bef8bb57e17f5a7f068a1719fbda384d40fc37acb8eb8aeca493b6ea"},"ecto_sql": {:hex, :ecto_sql, "3.12.1", "c0d0d60e85d9ff4631f12bafa454bc392ce8b9ec83531a412c12a0d415a3a4d0", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.12", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "aff5b958a899762c5f09028c847569f7dfb9cc9d63bdb8133bff8a5546de6bf5"},+ "elixir_make": {:hex, :elixir_make, "0.9.0", "6484b3cd8c0cee58f09f05ecaf1a140a8c97670671a6a0e7ab4dc326c3109726", [:mix], [], "hexpm", "db23d4fd8b757462ad02f8aa73431a426fe6671c80b200d9710caf3d1dd0ffdb"},"esbuild": {:hex, :esbuild, "0.8.2", "5f379dfa383ef482b738e7771daf238b2d1cfb0222bef9d3b20d4c8f06c7a7ac", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "558a8a08ed78eb820efbfda1de196569d8bfa9b51e8371a1934fbb31345feda7"},"expo": {:hex, :expo, "1.1.0", "f7b9ed7fb5745ebe1eeedf3d6f29226c5dd52897ac67c0f8af62a07e661e5c75", [:mix], [], "hexpm", "fbadf93f4700fb44c331362177bdca9eeb8097e8b0ef525c9cc501cb9917c960"},"file_system": {:hex, :file_system, "1.1.0", "08d232062284546c6c34426997dd7ef6ec9f8bbd090eb91780283c9016840e8f", [:mix], [], "hexpm", "bfcf81244f416871f2a2e15c1b515287faa5db9c6bcf290222206d120b3d43f6"},"finch": {:hex, :finch, "0.19.0", "c644641491ea854fc5c1bbaef36bfc764e3f08e7185e1f084e35e0672241b76d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "fc5324ce209125d1e2fa0fcd2634601c52a787aff1cd33ee833664a5af4ea2b6"},+ "fine": {:hex, :fine, "0.1.0", "9bb99a5ff9b968f12c3b458fa1277c39e9a620b23a9439103703a25917293871", [:mix], [], "hexpm", "1d6485bf811b95dc6ae3d197c0e6f994880b86167a827983bb29cbfc03a02684"},"floki": {:hex, :floki, "0.37.0", "b83e0280bbc6372f2a403b2848013650b16640cd2470aea6701f0632223d719e", [:mix], [], "hexpm", "516a0c15a69f78c47dc8e0b9b3724b29608aa6619379f91b1ffa47109b5d0dd3"},"gettext": {:hex, :gettext, "0.26.2", "5978aa7b21fada6deabf1f6341ddba50bc69c999e812211903b169799208f2a8", [:mix], [{:expo, "~> 0.5.1 or ~> 1.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "aa978504bcf76511efdc22d580ba08e2279caab1066b76bb9aa81c4a1e0a32a5"},"heroicons": {:git, "https://github.com/tailwindlabs/heroicons.git", "88ab3a0d790e6a47404cba02800a6b25d2afae50", [tag: "v2.1.1", sparse: "optimized", depth: 1]},@@ -31,6 +34,7 @@ "phoenix_template": {:hex, :phoenix_template, "1.0.4", "e2092c132f3b5e5b2d49c96695342eb36d0ed514c5b252a77048d5969330d639", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "2c0c81f0e5c6753faf5cca2f229c9709919aba34fab866d3bc05060c9c444206"},"plug": {:hex, :plug, "1.16.1", "40c74619c12f82736d2214557dedec2e9762029b2438d6d175c5074c933edc9d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a13ff6b9006b03d7e33874945b2755253841b238c34071ed85b0e86057f8cddc"},"plug_crypto": {:hex, :plug_crypto, "2.1.0", "f44309c2b06d249c27c8d3f65cfe08158ade08418cf540fd4f72d4d6863abb7b", [:mix], [], "hexpm", "131216a4b030b8f8ce0f26038bc4421ae60e4bb95c5cf5395e1421437824c4fa"},"postgrex": {:hex, :postgrex, "0.19.3", "a0bda6e3bc75ec07fca5b0a89bffd242ca209a4822a9533e7d3e84ee80707e19", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "d31c28053655b78f47f948c85bb1cf86a9c1f8ead346ba1aa0d0df017fa05b61"},+ "pythonx": {:hex, :pythonx, "0.4.2", "542667f1bb7d941cbba8a0b042eb487d463bd009f39ed5957a0d26dc6365973d", [:make, :mix], [{:cc_precompiler, "~> 0.1", [hex: :cc_precompiler, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.9", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:fine, "~> 0.1.0", [hex: :fine, repo: "hexpm", optional: false]}], "hexpm", "eb0ffda6d480df15d02dd252b5c2bc7058655f4253b36ee24e1ab301fae849ae"},"req": {:hex, :req, "0.5.8", "50d8d65279d6e343a5e46980ac2a70e97136182950833a1968b371e753f6a662", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "d7fc5898a566477e174f26887821a3c5082b243885520ee4b45555f5d53f40ef"},"swoosh": {:hex, :swoosh, "1.17.8", "7fe0541ddaf89f66117288a21d5a93244e256742281acaefa0cc0533f4ab882b", [:mix], [{:bandit, ">= 1.0.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mua, "~> 0.2.3", [hex: :mua, repo: "hexpm", optional: true]}, {:multipart, "~> 0.4", [hex: :multipart, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:req, "~> 0.5 or ~> 1.0", [hex: :req, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "2302789c3c2d44648f1bc09ad54fdb2f9029f9626d5469db141a1ca1f4aa3aa2"},"tailwind": {:hex, :tailwind, "0.2.4", "5706ec47182d4e7045901302bf3a333e80f3d1af65c442ba9a9eed152fb26c2e", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "c6e4a82b8727bab593700c998a4d98cf3d8025678bfde059aed71d0000c3e463"},