diff --git a/nbdev/doclinks.py b/nbdev/doclinks.py index 9e0e0dcf..937a86c2 100644 --- a/nbdev/doclinks.py +++ b/nbdev/doclinks.py @@ -210,8 +210,11 @@ def _build_lookup_table(strip_libs=None, incl_libs=None, skip_mods=None): skip_mods = setify(skip_mods) strip_libs = L(strip_libs) if incl_libs is not None: incl_libs = (L(incl_libs)+strip_libs).unique() - entries = {o.name: _qual_syms(o.resolve()) for o in list(pkg_resources.iter_entry_points(group='nbdev')) - if incl_libs is None or o.dist.key in incl_libs} + entries = {} + for o in pkg_resources.iter_entry_points(group='nbdev'): + if incl_libs is not None and o.dist.key not in incl_libs: continue + try: entries[o.name] = _qual_syms(o.resolve()) + except Exception: pass py_syms = merge(*L(o['syms'].values() for o in entries.values()).concat()) for m in strip_libs: if m in entries: diff --git a/nbs/api/05_doclinks.ipynb b/nbs/api/05_doclinks.ipynb index b9479c5e..7720ced8 100644 --- a/nbs/api/05_doclinks.ipynb +++ b/nbs/api/05_doclinks.ipynb @@ -52,6 +52,7 @@ "source": [ "#|hide\n", "from IPython.display import Markdown,display\n", + "from unittest.mock import patch as xpatch\n", "from fastcore.test import *\n", "from pdb import set_trace\n", "from importlib import reload\n", @@ -534,8 +535,11 @@ " skip_mods = setify(skip_mods)\n", " strip_libs = L(strip_libs)\n", " if incl_libs is not None: incl_libs = (L(incl_libs)+strip_libs).unique()\n", - " entries = {o.name: _qual_syms(o.resolve()) for o in list(pkg_resources.iter_entry_points(group='nbdev'))\n", - " if incl_libs is None or o.dist.key in incl_libs}\n", + " entries = {}\n", + " for o in pkg_resources.iter_entry_points(group='nbdev'):\n", + " if incl_libs is not None and o.dist.key not in incl_libs: continue\n", + " try: entries[o.name] = _qual_syms(o.resolve())\n", + " except Exception: pass\n", " py_syms = merge(*L(o['syms'].values() for o in entries.values()).concat())\n", " for m in strip_libs:\n", " if m in entries:\n", @@ -547,6 +551,84 @@ " return entries,py_syms" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#|hide\n", + "_build_lookup_table.cache_clear()\n", + "\n", + "# Test _build_lookup_table caching\n", + "initial = _build_lookup_table.cache_info()\n", + "_ = _build_lookup_table() # First call should miss\n", + "after_first = _build_lookup_table.cache_info()\n", + "_ = _build_lookup_table() # Second call should hit\n", + "after_second = _build_lookup_table.cache_info()\n", + "\n", + "test_eq(after_first.misses, initial.misses + 1)\n", + "test_eq(after_first.hits, initial.hits)\n", + "test_eq(after_second.hits, after_first.hits + 1)\n", + "test_eq(after_second.misses, after_first.misses)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's test out our error handling when one of the entry points throws an error:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Create mock entry points\n", + "class BadEntryPoint:\n", + " name = 'bad_entry'\n", + " dist = type('Dist', (), {'key': 'bad_lib'})()\n", + " def resolve(self): raise AttributeError(\"Simulated error\")\n", + "\n", + "class GoodEntryPoint:\n", + " name = 'good_entry'\n", + " dist = type('Dist', (), {'key': 'good_lib'})()\n", + " def resolve(self): return {'syms': {}, 'settings': {}}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'good_entry': {'syms': {}, 'settings': {}}}" + ] + }, + "execution_count": null, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Clear the cache before testing\n", + "_build_lookup_table.cache_clear()\n", + "\n", + "# Patch iter_entry_points\n", + "with xpatch('pkg_resources.iter_entry_points', return_value=[BadEntryPoint(), GoodEntryPoint()]):\n", + " entries, py_syms = _build_lookup_table()\n", + " \n", + " # Should only contain the good entry\n", + " assert 'bad_entry' not in entries\n", + " assert 'good_entry' in entries\n", + " assert len(entries) == 1\n", + "entries" + ] + }, { "cell_type": "code", "execution_count": null, @@ -599,37 +681,6 @@ " return '\\n'.join(lines)" ] }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/Users/nathan/miniconda3/lib/python3.12/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n", - " from .autonotebook import tqdm as notebook_tqdm\n" - ] - } - ], - "source": [ - "#|hide\n", - "_build_lookup_table.cache_clear()\n", - "\n", - "# Test _build_lookup_table caching\n", - "initial = _build_lookup_table.cache_info()\n", - "_ = _build_lookup_table() # First call should miss\n", - "after_first = _build_lookup_table.cache_info()\n", - "_ = _build_lookup_table() # Second call should hit\n", - "after_second = _build_lookup_table.cache_info()\n", - "\n", - "test_eq(after_first.misses, initial.misses + 1)\n", - "test_eq(after_first.hits, initial.hits)\n", - "test_eq(after_second.hits, after_first.hits + 1)\n", - "test_eq(after_second.misses, after_first.misses)" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -670,7 +721,7 @@ "text/markdown": [ "---\n", "\n", - "[source](https://github.com/fastai/nbdev/blob/master/nbdev/doclinks.py#L234){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", + "[source](https://github.com/fastai/nbdev/blob/master/nbdev/doclinks.py#L241){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", "\n", "### NbdevLookup.doc\n", "\n", @@ -681,7 +732,7 @@ "text/plain": [ "---\n", "\n", - "[source](https://github.com/fastai/nbdev/blob/master/nbdev/doclinks.py#L234){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", + "[source](https://github.com/fastai/nbdev/blob/master/nbdev/doclinks.py#L241){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", "\n", "### NbdevLookup.doc\n", "\n", @@ -699,6 +750,13 @@ "show_doc(NbdevLookup.doc)" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here's a test suite that verifies the error handling behavior:" + ] + }, { "cell_type": "code", "execution_count": null, @@ -765,7 +823,7 @@ "text/markdown": [ "---\n", "\n", - "[source](https://github.com/fastai/nbdev/blob/master/nbdev/doclinks.py#L239){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", + "[source](https://github.com/fastai/nbdev/blob/master/nbdev/doclinks.py#L246){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", "\n", "### NbdevLookup.code\n", "\n", @@ -776,7 +834,7 @@ "text/plain": [ "---\n", "\n", - "[source](https://github.com/fastai/nbdev/blob/master/nbdev/doclinks.py#L239){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", + "[source](https://github.com/fastai/nbdev/blob/master/nbdev/doclinks.py#L246){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", "\n", "### NbdevLookup.code\n", "\n", @@ -824,7 +882,7 @@ "text/markdown": [ "---\n", "\n", - "[source](https://github.com/fastai/nbdev/blob/master/nbdev/doclinks.py#L257){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", + "[source](https://github.com/fastai/nbdev/blob/master/nbdev/doclinks.py#L264){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", "\n", "### NbdevLookup.linkify\n", "\n", @@ -833,7 +891,7 @@ "text/plain": [ "---\n", "\n", - "[source](https://github.com/fastai/nbdev/blob/master/nbdev/doclinks.py#L257){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", + "[source](https://github.com/fastai/nbdev/blob/master/nbdev/doclinks.py#L264){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", "\n", "### NbdevLookup.linkify\n", "\n",