Mix.install([
{:kino, "~> 0.8"},
{:thousand_island, "~> 0.5"},
{:ecto, "~> 3.9"},
{:ecto_sql, "~> 3.9"},
{:postgrex, ">= 0.0.0"},
{:flow, "~> 1.2"}
])
IO.puts("Hello from Livebook!")
Can we do markdown?
yes we can!
# no code cell depends on this
x = 1
y = 2
z = 3
y + z
You still have access to the environment where you branched off
branch_state = z * 3
flowchart TD;
subgraph Section #1
c1[Cell A];
c2[Cell B];
c1-->c2;
end
subgraph branch [Branch from #1]
c5[Cell E];
c6[Cell F];
c2-->c5;
c5-->c6;
end
subgraph Section #2
c3[Cell C];
c4[Cell D];
c2-->c3;
c3-->c4;
end
classDef red fill:#f96;
class branch red;
(adapted from github.com/josevalim/livebooks)
frame = Kino.Frame.new() |> Kino.render()
for i <- Stream.interval(1000) do
Kino.Frame.render(frame, "never ending counter in a branch... #{i}")
end
IO.puts("this will never print... (it's queued though)")
"we're independent 💪 from that previous branching section"
But you don't get the bindings from that previous branching section...
# uncomment next line to see variable `branch_state` does not exist
# branch_state
Erlang VM processes and distribution everywhere
flowchart LR;
subgraph Clients
b1((Browser #1));
b2((Browser #2));
end
subgraph Livebook
l[Session];
b1--WebSockets-->l;
b2--WebSockets-->l;
end
subgraph Runtime
c[Code];
o1[Output #1];
o2[Output #2];
l--Erlang distribution-->c;
l--Erlang distribution-->o1;
l--Erlang distribution-->o2;
end
(adapted from github.com/josevalim/livebooks)
defmodule Echo do
use ThousandIsland.Handler
@impl true
def handle_data(data, socket, frame) do
Kino.Frame.render(frame, data)
ThousandIsland.Socket.send(socket, data)
{:continue, frame}
end
end
Make sure the child processes are started under the Kino supervisor. The process is automatically terminated when the current process terminates or the current cell reevaluates.
frame = Kino.Frame.new()
{:ok, server_pid} =
Kino.start_child(
{ThousandIsland, port: 1234, handler_module: Echo, num_acceptors: 2, handler_options: frame}
)
frame
Now you can telnet localhost 1234
to test this TCP server.
It's also interesting to inspect the supervision tree of the server. Notice the two acceptors (as configured above) and new processes are spawned under this tree, whenever a new connection is created.
Kino.Process.sup_tree(server_pid)
repo_config = [username: "postgres", password: "postgres", database: "fosdem_demo"]
defmodule Repo do
use Ecto.Repo, adapter: Ecto.Adapters.Postgres, otp_app: :does_not_matter
end
# emulates mix ecto.create
Repo.__adapter__().storage_up(repo_config)
defmodule ExampleMigration do
use Ecto.Migration
def change do
create table(:some_table) do
add(:name, :string)
add(:at, :utc_datetime)
end
execute("INSERT INTO some_table(name, at) VALUES ('my fresh entry', NOW()) ", "")
end
end
Kino.start_child({Repo, repo_config})
Ecto.Migrator.run(Repo, [{0, ExampleMigration}], :down, all: true, log: false)
Ecto.Migrator.run(Repo, [{0, ExampleMigration}], :up, all: true, log: false)
import Ecto.Query, only: [from: 2]
Ecto.Query.from(t in "some_table", select: [t.id, t.name, t.at])
|> Repo.all()
Stream.interval(1000)
|> Stream.take(3)
# this is a slow producer, so we'll lower the max_demand
|> Flow.from_enumerable(max_demand: 1)
|> Flow.map(fn counter ->
Repo.insert_all("some_table", [[name: "entry #{counter}", at: NaiveDateTime.utc_now()]])
end)
|> Flow.stream(link: false)
|> Stream.run()
import Ecto.Query, only: [from: 2]
Ecto.Query.from(t in "some_table",
select: %{id: t.id, name: t.name, at: t.at},
order_by: [desc: t.at]
)
|> Repo.all()
|> Kino.DataTable.new(keys: [:id, :name, :at])
Doctests are run automatically.
defmodule Christmas do
@doc """
iex> Christmas.santafy("ho")
"HO! HO! HO!"
iex> Christmas.santafy("no")
"NO! NO! NO!"
iex> Christmas.santafy("go")
"GO! GO! GO! GO!"
"""
def santafy(some_input) do
"#{some_input}!"
|> String.upcase()
|> List.duplicate(3)
|> Enum.join(" ")
end
end
# don't run tests automatically
ExUnit.start(auto_run: false)
defmodule ChristmasTest do
use ExUnit.Case
test "should make everything jolly" do
assert Christmas.santafy("ho") == "HO! HO! HO!"
end
test "example failing test" do
assert Christmas.santafy("go") == "GO! GO! GO!"
end
end
# now actually run the tests manually
ExUnit.run()
(adapted from this blogpost by Brooklin Myers, which has some more good tips on how to run tests in Livebook)