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.
Sachaa-Thanasius committed Sep 3, 2024
1 parent 07766fe commit 2295585
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
@@ -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
-------
11 changes: 11 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -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

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


__version__ = "0.0.1"
__version__ = "0.0.1dev"


# region -------- Compile-time hook
@@ -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:
@@ -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


18 changes: 8 additions & 10 deletions tests/test_deferred.py
Original file line number Diff line number Diff line change
@@ -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):

0 comments on commit 2295585

Please sign in to comment.