Best way to include local module (with struct) in Pluto

I am trying use Pluto with a local module.

I have a module like:

foo.jl

module foo

export foobar

    struct foobar
        A:Int
    end
end 

I think the recommended way to do this is:

notebook1.jl

include("foo.jl")
using .foo

However the issue here is that the my struct is only exported to the cell where I did using .foo. I still have to do foo.foobar to access the struct in a new Pluto cell.

So I have to do an import to manually import foo to the scope of the whole notebook.

notebook2.jl

include("foo.jl")
using .foo
import .foo: foobar

This is fine for one struct but in reality I have a module with a bunch of stuff to import and I don’t want the added overhead of explicitly importing.

I have looked all around and found various recommendations on how to accomplish this. Fonsp had the idea of the ingredients function to sort of extend the import function to work better with Pluto reactivity.

github.com/fonsp/Pluto.jl

karlwessel karlwessel

reactivity

It currently is not possible to reevaluate a cell that includes a file which def

notebook3.jl

function ingredients(path::String)
	# this is from the Julia source code (evalfile in base/loading.jl)
	# but with the modification that it returns the module instead of the last object
	name = Symbol(basename(path))
	m = Module(name)
	Core.eval(m,
        Expr(:toplevel,
             :(eval(x) = $(Expr(:core, :eval))($name, x)),
             :(include(x) = $(Expr(:top, :include))($name, x)),
             :(include(mapexpr::Function, x) = $(Expr(:top, :include))(mapexpr, $name, x)),
             :(include($path))))
	m
end

foo = ingredients("foo.jl")

import .foo: foobar

This gives warning `could not import foo.jl.foobar into workspace

I think the exact syntax for importing local modules in julia in general has changed: (include, using, import, .using, etc.) since that discussion and I’m wondering about what is the current best practice.

2 Likes

This is certainly not the best way, but it might suit your needs to add the location of the local module(s) to the LOAD_PATH. It’s basically faking the presence of a package in the given folder, which also works with single files, as long as the file name matches the module name (as described here Code Loading · The Julia Language ).

The limitations I can think of right now are (basically the same as for any package)

  • only one module per file/folder possible (nesting and re-exporting would work though)
  • the file/folder name must match the module name
  • the file/folder name should not match the name of an existing package in one of your registries or other load path locations (otherwise probably the wrong package gets used)

Here’s an example notebook that sets up and uses the local module (note that the display order is a bit weird… first is the struct definition, then saving to file, pushing to LOAD_PATH, then using):

### A Pluto.jl notebook ###
# v0.19.27

using Markdown
using InteractiveUtils

# ╔═╡ 688fef7a-3456-490b-82f8-029b77d0910c
push!(LOAD_PATH, pwd());

# ╔═╡ 84628da7-d690-4e13-8bc2-358c86afc711
using FooModule

# ╔═╡ 77631f94-577a-11ee-0b11-3be3c11e936b
structDef = """
module FooModule

export A

struct A
    i::Int
end

end
"""

# ╔═╡ 567b7c95-045f-42f3-87f2-920f86a8cdcc
write("FooModule.jl", structDef)

# ╔═╡ 2d80e649-4a0a-42f5-b70d-86fb298d1177
A(1)

# ╔═╡ Cell order:
# ╠═77631f94-577a-11ee-0b11-3be3c11e936b
# ╠═567b7c95-045f-42f3-87f2-920f86a8cdcc
# ╠═688fef7a-3456-490b-82f8-029b77d0910c
# ╠═84628da7-d690-4e13-8bc2-358c86afc711
# ╠═2d80e649-4a0a-42f5-b70d-86fb298d1177

EDIT: At least when playing around a little bit right now it seems to work fine. It also works to modify certain things in the module if we do using Revise is in the notebook.

1 Like

Thanks, yeah that’s certainly a better way than I’ve found!

I had played with the load path a bit but “tricking it” with pwd() is clever. My use case is a notebook template that is used on different machines so I don’t want it to be a headache to import my base library every time someone uses it.

1 Like

Using the current directory seemed to be a good option, but I didn’t really think about it – at least Pluto notebooks are supposed to be self-contained and this felt like the next best thing :sweat_smile:

I don’t know how you are distributing your base library to the machines/users, but if you have some sort of shared git server, maybe LocalRegistry.jl might be useful. Turning your library into a package has a bit of an overhead of course (setting up the local registry and, optionally, versioning the library), but in my case this setup cost was well worth it. Being able to just do a quick pkg> add XYZ and using XYZ anywhere within my network is pretty convenient.

2 Likes