Skip to content

Initial setup of lazy_import functions.#4909

Merged
dschult merged 8 commits intonetworkx:mainfrom
dschult:lazy
Jan 25, 2022
Merged

Initial setup of lazy_import functions.#4909
dschult merged 8 commits intonetworkx:mainfrom
dschult:lazy

Conversation

@dschult
Copy link
Copy Markdown
Member

@dschult dschult commented Jun 17, 2021

Lazy load of potentially uninstalled libraries.
Also, a function to set up the internal sub-packages in a lazy-load fashion.

This first version will fail tests in environments without numpy because pytest.importorskip is not lazy yet.

Still needs:

  • way to handle pytest.importorskip
  • Loader class instead of monkey patch on SourceFileLoader
  • Way to identify a lazily-loaded-not-yet-used module

dschult added 2 commits June 18, 2021 08:14
Still needs:
- way to handle pytest.importorskip
- Loader class instead of monkey patch on SourceFileLoader
- Way to identify a lazily-loaded-not-yet-used module
@dschult
Copy link
Copy Markdown
Member Author

dschult commented Jun 18, 2021

I believe the test failures here are due to not replacing pytest.importorskip uniformly throughout the library. So some modules are using nx.lazy_importorskip and others aren't. I've tested it locally on utils/tests/test_misc.py and on linalg/tests/test_algebraic_connectivity.py and it works for those.

I've settled on mimicking the importlib.LazyLoader and _LazyModule code as much as possible. Two new subclasses of these enable introspection of the "spec" without triggering the ModuleNotFoundError. That allows a function lazy_importorskip in addition to the two functions from scikit-image (morphed accordingly) lazy_import() and I renamed the other to lazy_package_import().

They should be fairly easy to read now. I'll start workign on figuring out how much of the code has to be changed to enable lazy_importorskip and I'll think a little about whether we can suggest this change to the original pytest.importorskip. Probably no new pushed from me until Sunday. Feedback welcome. :}

Copy link
Copy Markdown
Contributor

@stefanv stefanv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work, Dan! This looks great already. I just had a few questions (mainly for my own enlightenment).

pass


def test_lazy_package_import():
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is nice; would be good to get something like that over to the skimage PR too!

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is nice; would be good to get something like that over to the skimage PR too!

I'm going to try to get something like these tests of attach over in the skimage PR as suggested.

Should I also update the lazy.load function there to allow "delayed failed modules"? It currently raises a ModuleNotFoundError immediately upon lazy.load when a library is not found. That works well for many cases, but not if you want to import optional libraries. Thoughts @stefanv ?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that would be a good feature to have, thanks Dan!

@dschult
Copy link
Copy Markdown
Member Author

dschult commented Jun 22, 2021

Some of these ideas might be helpful in the PR over at: scikit-image/scikit-image#5101

@dschult
Copy link
Copy Markdown
Member Author

dschult commented Dec 26, 2021

Looks like the differences between the NX lazy-load and scikit lazy-load are:

  • attach first argument name is module_name rather than package_name
  • load raises ModuleNotFoundError for uninstalled libraries in both, but this version delays the ImportError until it is used. The skimage version raises upon the calling of load.

What are the next steps?

  1. replace all non-builtin imports with lazy imports. Should they be moved out of the functions to the top of the module? We lose easy search for where they are used, but gain that all import related commands appear at top of module.
  2. load the NetworkX subpackage code in a lazy way. It may be worth going through our prior choices of what gets put in the top level namespace as part of this.

Note: number 1) is done to utils/misc.py in this PR to show what it might look like.

Finally, shall I make these "next steps" in this PR? I'm thinking maybe making another PR that relies on this one. It will touch a lot of code to change the imports.

Copy link
Copy Markdown
Contributor

@stefanv stefanv left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good to me.

As @dschult mentioned, the biggest change from the skimage approach here is that import exceptions are delayed. This means that a user may be able to import a subpackage successfully, but that code will break once it is used. In practice (a) it will allow more code to execute successfully [if your code doesn't use a specific import, it won't fail] (b) raise errors at a different time to what users are currently accustomed to (on function execution rather than module import). I think (b) is not an issue for two reasons: (1) these imports used to reside inside functions before load anyway and (2) the difference will only really be noticeable in interactive work, where raising the exception later makes more sense.

OK, I may have convinced myself to port that change over to skimage as well ;)

@dschult
Copy link
Copy Markdown
Member Author

dschult commented Jan 21, 2022

Thanks @Stefan,
There is another trait of this way to lazy-load. It means that new contributors who are used to using import numpy as np at the top of the module will need to prompted to change that syntax to np = nx.lazy_imports.load("numpy").

But I guess they are currently being prompted to move the import inside their functions. So that's likely to be about the same maintenance burden. The CI will check it either way too.
:}

@stefanv
Copy link
Copy Markdown
Contributor

stefanv commented Jan 21, 2022

But I guess they are currently being prompted to move the import inside their functions. So that's likely to be about the same maintenance burden. The CI will check it either way too. :}

True :) I commented on the other PR that slightly nicer syntax may be something like nx.lazy_import(x) or nx.lazy_load(x).

@dschult
Copy link
Copy Markdown
Member Author

dschult commented Jan 22, 2022

I was using the function name "load" because of the version in skimage. But maybe that's not the best name...

Why is the term "load" used? That seems to normally refer to bringing data from a file into a variable. The standard term in Python seems to be "import". Is there a reason to avoid the term "import" here, e.g. nx.lazy_import("numpy")? What was the thought behind using the name load?

@stefanv
Copy link
Copy Markdown
Contributor

stefanv commented Jan 22, 2022

We went through various discussions on naming and that is what we settled on, but I don't think there's any particularly good reason for it other to distinguish it from a traditional import. lazy_import should be fine.

@dschult dschult merged commit 1491775 into networkx:main Jan 25, 2022
@dschult dschult added this to the networkx-2.7 milestone Jan 25, 2022
MridulS pushed a commit to MridulS/networkx that referenced this pull request Feb 4, 2023
* Initial setup of lazy_import functions.

Still needs:
- way to handle pytest.importorskip --> soln is not to load delayed modules into sys.modules
- Loader class instead of monkey patch on SourceFileLoader
- Way to identify a lazily-loaded-not-yet-used module  --> now create an instance of the Delayed Module class.

* fix importorskip with new module class

* Remove lazy_importorskip. Don't add Delayed reporting module to sys.modules

* make tests work for pypy

* refactor to include changes from skimage.lazy

* fix test handling of types.ModuleType

* Change name from nx.lazy_imports.load to nx.lazy_import

* fix tests to use new name. keep `attach` name as is.
@dschult dschult deleted the lazy branch February 21, 2024 20:05
cvanelteren pushed a commit to cvanelteren/networkx that referenced this pull request Apr 22, 2024
* Initial setup of lazy_import functions.

Still needs:
- way to handle pytest.importorskip --> soln is not to load delayed modules into sys.modules
- Loader class instead of monkey patch on SourceFileLoader
- Way to identify a lazily-loaded-not-yet-used module  --> now create an instance of the Delayed Module class.

* fix importorskip with new module class

* Remove lazy_importorskip. Don't add Delayed reporting module to sys.modules

* make tests work for pypy

* refactor to include changes from skimage.lazy

* fix test handling of types.ModuleType

* Change name from nx.lazy_imports.load to nx.lazy_import

* fix tests to use new name. keep `attach` name as is.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Development

Successfully merging this pull request may close these issues.

2 participants