Notebook-specific target/Project.toml (similar to test)

Greetings,

I am developing a Julia package in which I have some Jupyter notebooks. Those notebooks use some packages that I do not want to include as general project dependencies (e.g., Plots.jl). However, I do not seem to find a way to manage that.

So far, I have tried the two approaches suggested for handling tests. Given that notebooks are in a notebooks/ folder, I first tried adding a new target to match that, but the Jupyter notebooks do not activate the project. Then, I tried having another Project.toml file inside the notebooks dir, but since the notebook won’t merge automatically with the parent Project.toml file, I don’t even know how to include the parent package as a dependency.

This is the way to go. To add the parent directory you use its relative path ... You probably want to use dev rather than add, since you presumably want your notebooks to use the current state of the files rather than a specific commit or release. Thus, the commands will look something like this:

$ cd notebooks

$ julia --project=.
julia> # hit ] to enter pkg mode

(notebooks) pkg> dev ..
   Resolving package versions...
    Updating `~/ParentPackage/notebooks/Project.toml`
  [336d156d] + ParentPackage v0.1.0 `..`
    Updating `~/ParentPackage/notebooks/Manifest.toml`
  [...]

For this to work, the parent directory must contain a package as generated by pkg> generate or PkgTemplates.jl, not just a barebones project environment. Specifically, the parent directory’s Project.toml must contain a name and an UUID.

3 Likes

Use the global env for those packages like Plots.jl or setup GitHub - GunnarFarneback/LocalRegistry.jl: Create and maintain local registries for Julia packages. and create a separate package for notebooks.

Although I wish I could take the target approach, I’m currently keeping the separate Project.toml file, as suggested by @danielwe.

In case it’s useful for anyone facing the same challenge, I indeed added the parent package as a development dependency, and to access its dependencies I just do using A.B (found out about that here).

If you’re actually using B in your notebooks, why not add it explicitly to the notebook env? By relying on using A.B you’re introducing a very tight coupling between the notebooks and implementation details of A, which will be a hassle whenever you want to refactor and reconsider dependencies in A.

The [targets] approach is not expected to work. As far as I understand it currently only supports test.

I use the notebooks for usage examples of A and for some development (experimentation), so it is relevant to me, for example, that the version of B in the notebooks is the same as in A.

That will always be the case. A julia environment will only ever contain one version of a package. When you do

(notebooks) pgk> dev ..  # contains A, which depends on B

(notebooks) pkg> add B

then the second command does almost nothing—B was already installed in the environment when you did dev .. because it’s needed by A (you can run pkg> status --manifest to see the complete list of installed packages). The only difference after add B is that you can now do using B instead of having to access B indirectly as A.B.

I thought one of the reasons behind not making the dependencies of a package already available was to allow each package to have different versions of a shared dependency. So I believed there would be no guarantee that, once I update B in A, the version of B in notebooks would also be updated.

Also, is that the case for unregistered dependencies?

No, this is not currently possible, though it does come up as a feature request from time to time. Currently, only a single version of each package can be loaded in any julia session.

I’m pretty sure it’s the case for any code loaded via using or import. To get around it you’d have to manually copy/include the relevant code such that it becomes part of your package rather than a managed dependency.

In fact, if this weren’t the case it wouldn’t be possible for julia packages to compose the way they do, where package C can use a method that package B adds to a function defined in package A.