Skip to content

Commit

Permalink
Try calling the path hook in a .pth file to avoid needing to invalida…
Browse files Browse the repository at this point in the history
…te caches. - Adjust documentation to match.
  • Loading branch information
Sachaa-Thanasius committed Sep 3, 2024
1 parent 07766fe commit 2295585
Show file tree
Hide file tree
Showing 4 changed files with 33 additions and 14 deletions.
6 changes: 5 additions & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,18 @@ Usage
Setup
-----

``defer-imports`` hooks into the Python import system with a path hook. That path hook needs to be registered before code using the import-delaying context manager, ``defer_imports.until_use``, is parsed. To do that, include the following somewhere such that it will be executed before your code:
This library uses a ``.pth`` file to register an import hook on interpreter startup. The hook is put in front of the built-in file finder's `path hook <https://docs.python.org/3/library/importlib.html#importlib.machinery.FileFinder.path_hook>`_ on `sys.path_hooks <https://docs.python.org/3/library/sys.html#sys.path_hooks>`_, and is responsible for the instrumentation side of this library. That should work fine if you're using a regular setup with site packages, where that ``.pth`` file should end up.

However, if your environment is atypical, you might need to manually register that path hook to have your code be correctly processed by this package. Do so in a file away from the rest of your code, before any of it executes. For example:

.. code:: python
import defer_imports
defer_imports.install_defer_import_hook()
import your_code
Example
-------
Expand Down
11 changes: 11 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,17 @@ Source = "https://github.com/Sachaa-Thanasius/defer-imports"
[tool.hatch.build.targets.wheel]
packages = ["src/defer_imports"]

[tool.hatch.build.targets.wheel.hooks.autorun]
# Install defer-import's path hook at startup. With luck, this'll bypass any need for cache invalidation.
dependencies = ["hatch-autorun"]
code = """
try:
import defer_imports
except ImportError:
pass
else:
defer_imports.install_defer_import_hook(invalidate_caches=False)
"""

# -------- Benchmark config

Expand Down
12 changes: 9 additions & 3 deletions src/defer_imports/_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from . import _typing as _tp


__version__ = "0.0.1"
__version__ = "0.0.1dev"


# region -------- Compile-time hook
Expand Down Expand Up @@ -664,10 +664,15 @@ def deferred___import__( # noqa: ANN202
# region -------- Public API


def install_defer_import_hook() -> None:
def install_defer_import_hook(*, invalidate_caches: bool = True) -> None:
"""Insert defer_imports's path hook right before the default FileFinder one in sys.path_hooks.
This can be called in a few places, e.g. __init__.py of a package, a .pth file in site packages, etc.
Parameters
----------
invalidate_caches: bool, default=True
Whether to invalidate the caches on all path entry finders.
"""

if DEFERRED_PATH_HOOK in sys.path_hooks:
Expand All @@ -678,7 +683,8 @@ def install_defer_import_hook() -> None:
for i, hook in enumerate(sys.path_hooks):
if hook.__qualname__.startswith("FileFinder.path_hook"):
sys.path_hooks.insert(i, DEFERRED_PATH_HOOK)
PathFinder.invalidate_caches()
if invalidate_caches:
PathFinder.invalidate_caches()
return


Expand Down
18 changes: 8 additions & 10 deletions tests/test_deferred.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,29 +183,27 @@ def test_instrumentation(before: str, after: str):
def test_path_hook_installation():
"""Test the API for putting/removing the defer_imports path hook from sys.path_hooks."""

# It shouldn't be on there by default.
assert DEFERRED_PATH_HOOK not in sys.path_hooks
before_length = len(sys.path_hooks)

# It should be present after calling install.
install_defer_import_hook()
before_path_hooks = list(sys.path_hooks)
# Thanks to the .pth file, it should be on there by default.
assert DEFERRED_PATH_HOOK in sys.path_hooks
assert len(sys.path_hooks) == before_length + 1
before_length = len(sys.path_hooks)

# Calling install shouldn't do anything if it's already on sys.path_hooks.
install_defer_import_hook()
assert DEFERRED_PATH_HOOK in sys.path_hooks
assert len(sys.path_hooks) == before_length + 1
assert len(sys.path_hooks) == before_length

# Calling uninstall should remove it.
uninstall_defer_import_hook()
assert DEFERRED_PATH_HOOK not in sys.path_hooks
assert len(sys.path_hooks) == before_length
assert len(sys.path_hooks) == before_length - 1

# Calling uninstall if it's not present should do nothing to sys.path_hooks.
uninstall_defer_import_hook()
assert DEFERRED_PATH_HOOK not in sys.path_hooks
assert len(sys.path_hooks) == before_length
assert len(sys.path_hooks) == before_length - 1

sys.path_hooks = before_path_hooks


def test_empty(tmp_path: Path):
Expand Down

0 comments on commit 2295585

Please sign in to comment.