Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

What's new in Livebook v0.7

whats_new_in_livebook_v07.livemd

What’s new in Livebook v0.7

Mix.install([
  {:kino, "~> 0.7.0"},
  {:kino_db, "~> 0.2.0"},
  {:explorer, "~> 0.3.1"},
  {:req, "~> 0.3.1"},
  {:postgrex, "~> 0.16.5"},
  {:req_athena, "~> 0.1.1"}
])

Intro

This is an accompanying notebook to the Livebook v0.7 announcement blog post.

Secrets management

Secrets inside Code cells

We know we should not hardcode passwords, API tokens, and sensitive data in our code or notebook like this:

api_username = "postman"
api_password = "password"

Req.get!("https://postman-echo.com/basic-auth", auth: {api_username, api_password})

To deal with sensitive data, Livebook has a feature called Secrets.

You can add a secret using the Secrets menu on the sidebar.

Once you have created a secret, you can use it using the System.fetch_env!/1, adding a “LB_” namespace to the name of the secret you created.

For example, let’s say you create a secret with the name API_USERNAME. You can call that secret from your code like this:

api_username = System.fetch_env!("LB_API_USERNAME")

Livebook will automatically detect when your code is trying to use a secret that was not setted up before.

For example, if you evaluate the cell below and the API_USERNAME or API_PASSWORD secret were not setted up, Livebook you ask you to create that secret.

You can create those secrets with the following value and evaluate the cell below again:

  • API_USERNAME: postman
  • API_PASSWORD: password
api_username = System.fetch_env!("LB_API_USERNAME")
api_password = System.fetch_env!("LB_API_PASSWORD")

Req.get!("https://postman-echo.com/basic-auth", auth: {api_username, api_password})

Secrets inside Database Connection Smart cell

The new Secrets feature is integrated with Database Connection Smart cells.

So, when you’re creating a connection to PostgreSQL or Amazon Athena, Livebook will give you the option to use a Secret for the database password.

To see how that works, evaluate the following cell and click in the Password field in the form below:

opts = [
  hostname: "localhost",
  port: 5432,
  username: "postgres",
  password: "",
  database: ""
]

{:ok, conn} = Kino.start_child({Postgrex, opts})

Or, evaluate the following cell and click in the Secret Access Key field in the form below:

Visual representations of the running system

An example of visualizing two processes exchanging a few messages

Let’s say you want to visualize how that piece of code is exchanging messages when it runs:

parent = self()

child =
  spawn(fn ->
    receive do
      :ping -> send(parent, :pong)
    end
  end)

send(child, :ping)

receive do
  :pong -> :ponged!
end

All you need to do is wrap your code with Kino.Process.render_seq_trace/2:

Kino.Process.render_seq_trace(fn ->
  parent = self()

  child =
    spawn(fn ->
      receive do
        :ping -> send(parent, :pong)
      end
    end)

  send(child, :ping)

  receive do
    :pong -> :ponged!
  end
end)

An example of visualizing more than two processes exchanging messages

Kino.Process.render_seq_trace(fn ->
  1..4
  |> Task.async_stream(fn i ->
    i
  end)
  |> Stream.run()
end)

Visualize supervision trees

To visualize a supervision tree, call Kino.Process.render_sup_tree/2 with the supervisor’s PID:

{:ok, supervisor_pid} =
  Supervisor.start_link(
    [
      {Task, fn -> Process.sleep(:infinity) end},
      {Agent, fn -> [] end}
    ],
    strategy: :one_for_one
  )

Kino.Process.render_sup_tree(supervisor_pid)

Automatic detection of a supervisor through its PID

Livebook will also automatically show you a supervision tree if the last line in your code cell is the PID of a supervisor:

{:ok, supervisor_pid} =
  Supervisor.start_link(
    [
      {Task, fn -> Process.sleep(:infinity) end},
      {Agent, fn -> [] end}
    ],
    strategy: :one_for_one
  )
supervisor_pid

Automatic detection of an application tree through its atom

Given the name of an application as an atom, Livebook will automatically render the application tree:

:kino

Interactive user interface to visualize and edit Elixir pipelines

An example of using Livebook’s dgb interactive user interface with a simple pipeline

"Elixir is cool!"
|> String.trim_trailing("!")
|> String.split()
|> List.first()
|> dbg()

An example of using Livebook’s dgb interactive user interface with a pipeline that is doing some data analysis

alias Explorer.DataFrame
alias Explorer.Series

Explorer.Datasets.iris()
|> DataFrame.filter_with(&Series.equal(&1["species"], "Iris-virginica"))
|> DataFrame.select(["sepal_length", "sepal_width", "petal_length", "petal_width"])
|> DataFrame.arrange(desc: "sepal_width")
|> DataFrame.rename(["sepal length", "sepal width", "petal lenght", "petal width"])
|> DataFrame.to_rows()
|> dbg()

Organize your cell output with layouts

Tabs layout

data = [
  %{id: 1, name: "Elixir", website: "https://elixir-lang.org"},
  %{id: 2, name: "Erlang", website: "https://www.erlang.org"}
]

Kino.Layout.tabs(
  Table: Kino.DataTable.new(data),
  Raw: data
)

Grid layout

urls = [
  "https://images.unsplash.com/photo-1603203040743-24aced6793b4?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=580&h=580&q=80",
  "https://images.unsplash.com/photo-1578339850459-76b0ac239aa2?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=580&h=580&q=80",
  "https://images.unsplash.com/photo-1633479397973-4e69efa75df2?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=580&h=580&q=80",
  "https://images.unsplash.com/photo-1597838816882-4435b1977fbe?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=580&h=580&q=80",
  "https://images.unsplash.com/photo-1629778712393-4f316eee143e?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=580&h=580&q=80",
  "https://images.unsplash.com/photo-1638667168629-58c2516fbd22?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=580&h=580&q=80"
]

images =
  for {url, i} <- Enum.with_index(urls, 1) do
    image = Kino.Markdown.new("![](#{url})")
    label = Kino.Markdown.new("**Image #{i}**")
    Kino.Layout.grid([image, label], boxed: true)
  end

Kino.Layout.grid(images, columns: 3)