Cannot resolve import (bug? for mutual importing notebooks)

I have a particular function in a notebook I wrote that I can import directly, like here. However, when I import it indirectly like here or side-by-side with an indirect import like here, I get an error that the imported function cannot be resolved.
I do not know how I can debug this. I think it might be the case that the import setup I have is causing this error:

I have a notebook A that serves as a central place for utility methods. Many of the utility methods are defined in other notebooks.
Let’s say I have a utility function a in A and I want to create a new utility method b in a new notebook B that builds on top of a. I hence import a in B from A, and once done, I import b in A from B so that b is now also accessible from the central utility methods notebook A.

In my case:

I am not sure if this is a bug or just me organising my imports in the wrong way. Any help appreciated!

1 Like

I suspect that the issue is the circular dependency between @kappelmann/lean-utility-functions and @kappelmann/lean-code-explainer, both of which import-with each other.

In lean-utility-functions:

import {explanationFooter} with {leanLibName} from '@kappelmann/lean-code-explainer'

And in lean-code-explainer:

import {leanEditor, mdtex, stepEditor}
with {leanLibName}
from '@kappelmann/lean-utility-functions'

Circular import-with’s aren’t supported because each import-with effectively creates a new local module to the notebook. I imagine you needed to use the circular import-with previously to workaround the bug that was fixed yesterday. You should now be able to change one of these notebooks to be the “primary” definition of the leanLibName variable, and avoid the circular import-with.

(I’ll investigate these issues further later today. The change in behavior of the direct import when it’s side-by-side with the indirect import is particularly surprising and likely a bug on our side. Another way you can debug this problem is to download your notebook as a tarball and run it locally… but you’ll need to wade into the internals of the Observable runtime, so I’ll try to help!)

2 Likes

When I turn on “Pause on exceptions” in the debugger for your “direct import” link (notebook 60d631381cf19061), I get an exception from (the minified version of) variable_undefined. Above it in the call stack is variable_compute where variable (minified to e in the Observable app) has _name equal to 'stepEditor'. I’m surprised that the e._module._runtime._variables Set has 74k+ elements. 161 of them have _name equal to 'stepEditor' and 129 of them have _name equal to explanationFooter – I guess when using circular import-with, the number of variables can blow up quickly (quadratically?).

Something else that’s strange: If I open lean-utility-functions in Firefox and then create a cell containing explanationFooter, it fails to resolve (seems to be the same issue involving some copy of stepEditor as above). However, creating an explanationFooter cell in lean-utility-functions using Chrome works fine.

1 Like

The root of the issue here is that you can’t use import-with together with circular imports. You can use circular imports, or you can use import-with, but you can’t use both together.

Import-with creates a local copy of the module, so if you have circular import-with, you essentially create an infinite recursive copy of the imported module and its imports. The runtime currently bails out when this happens, giving you a partially-initialized module, rather than going into an infinite loop. Hence the nondeterministic behavior.

Alas, I don’t see a sensible way to support circular import-with, so I’d like to change the runtime to fail more deterministically when circular imports are used in conjunction with import-with. Unfortunately this means that your notebook will break, but I’d be happy to work with you to revise your notebook so that it avoids circular imports and/or import-with.

2 Likes

Thank you so much (again) @bgchen and @mbostock ! I think it would be great in that case if the runtime fails deterministically with some sensible error message so that it is easy for users to detect the circular import. It should then also be mentioned in appropriate places, e.g. the introduction to imports page, that circular import ... with ... statements will cause an error.

I did change my setup now so that I do not run into this issue again.

PS. Support on observable is great :slight_smile:

1 Like

We’ve shipped the fix for circular import-with (and updated the Introduction to Imports to mention this restriction, as suggested).

If you attempt to use import-with in conjunction with circular imports, any symbols you import will not resolve and will appear gray:

In addition, you will see a warning in the console:

circular module definition; ignoring

Unfortunately, the notebooks in your first post still appear to have circular import-with‘s, and are now failing in a predictable way… but at least it’s not failing in an unpredictable way, and the gray imports should guide you towards restructuring the notebook and fixing this problem. Let me know if you need any further assistance.

(I plan on building something to visualize imports between notebooks, similar to the notebook visualizer, to further assist in debugging these problems. If someone has already written one, please share!)

4 Likes

Cool - making it predictable helped me fixing all the imports now. It forced me to make a cleaner separation between the mutually referencing notebooks, so that’s another win. Thanks Mike :slight_smile:

3 Likes

Thanks for your patience with this issue!

@mbostock: A while back I forked your Notebook visualizer in an attempt to show imported cells:

I never ended up publishing it though because graphviz wasn’t up to the task of drawing the output (see the comments at the start of the notebook) and I never got round to finding a suitable replacement.

Also, the above fork works with runtime v1 JS modules so it no longer captures the way imports on observablehq.com work today. Spurred by your post, this weekend I’ve been playing around with parsing and processing v3 JS modules. I’ll edit a link into this post when I have something to share.

edit: I made a new thread.

1 Like

Thanks, @bgchen! I’ve updated mine to use v=3 and show imports. I’d like to add cluster and maybe some other visual improvements still.

1 Like