diff --git a/docs/source/_exts/only_warn_on_broken_internal_refs.py b/docs/source/_exts/only_warn_on_broken_internal_refs.py new file mode 100644 index 000000000..c7882069e --- /dev/null +++ b/docs/source/_exts/only_warn_on_broken_internal_refs.py @@ -0,0 +1,50 @@ +from docutils import nodes +from sphinx import addnodes +from sphinx.application import Sphinx +from sphinx.transforms.post_transforms import SphinxPostTransform +from sphinx.util import logging + + +logger = logging.getLogger(__name__) + + +def is_public_internal_ref_target(target: str) -> bool: + return target.startswith("idom.") and not target.rsplit(".", 1)[-1].startswith("_") + + +class OnlyWarnOnBrokenInternalRefs(SphinxPostTransform): + """ + Warns about broken cross-reference links, but only for idom. + This is very similar to the sphinx option ``nitpicky=True`` (see + :py:class:`sphinx.transforms.post_transforms.ReferencesResolver`), but there + is no way to restrict that option to a specific package. + """ + + # this transform needs to happen before ReferencesResolver + default_priority = 5 + + def run(self) -> None: + for node in self.document.traverse(addnodes.pending_xref): + target = node["reftarget"] + + if is_public_internal_ref_target(target): + # let the domain try to resolve the reference + found_ref = self.env.domains[node["refdomain"]].resolve_xref( + self.env, + node.get("refdoc", self.env.docname), + self.app.builder, + node["reftype"], + target, + node, + nodes.TextElement("", ""), + ) + + # warn if resolve_xref did not return or raised + if not found_ref: + logger.warning( + f"API link {target} is broken.", location=node, type="ref" + ) + + +def setup(app: Sphinx) -> None: + app.add_post_transform(OnlyWarnOnBrokenInternalRefs) diff --git a/docs/source/architectural-patterns.rst b/docs/source/architectural-patterns.rst index 4e9d5292a..5a2986718 100644 --- a/docs/source/architectural-patterns.rst +++ b/docs/source/architectural-patterns.rst @@ -57,9 +57,9 @@ As a result, IDOM was developed to help solve these problems. Ecosystem Independence ---------------------- -IDOM has a flexible set of :ref:`core abstractions ` that allow it to -interface with its peers. At the time of writing Jupyter, Dash, and Bokeh (via Panel) -are supported, while Streamlit is in the works: +IDOM has a flexible set of :ref:`Core Abstractions` that allow it to interface with its +peers. At the time of writing Jupyter, Dash, and Bokeh (via Panel) are supported, while +Streamlit is in the works: - idom-jupyter_ (try it now with Binder_) - idom-dash_ diff --git a/docs/source/conf.py b/docs/source/conf.py index ce3d27afa..a7cd8cc2c 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -65,6 +65,7 @@ "patched_html_translator", "widget_example", "build_custom_js", + "only_warn_on_broken_internal_refs", ] # Add any paths that contain templates here, relative to this directory. diff --git a/docs/source/core-concepts.rst b/docs/source/core-abstractions.rst similarity index 98% rename from docs/source/core-concepts.rst rename to docs/source/core-abstractions.rst index fa1bb5ce5..c8983ba3c 100644 --- a/docs/source/core-concepts.rst +++ b/docs/source/core-abstractions.rst @@ -1,8 +1,5 @@ -Core Concepts -============= - -This section covers core features of IDOM that are used in making interactive -interfaces. +Core Abstractions +================= Pure Components diff --git a/docs/source/index.rst b/docs/source/index.rst index 3fb55fb2c..fb8c6c654 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -15,7 +15,7 @@ IDOM :hidden: :caption: Advanced Topics - core-concepts + core-abstractions javascript-components architectural-patterns specifications diff --git a/docs/source/life-cycle-hooks.rst b/docs/source/life-cycle-hooks.rst index 77bdc678e..5a8be3169 100644 --- a/docs/source/life-cycle-hooks.rst +++ b/docs/source/life-cycle-hooks.rst @@ -284,10 +284,9 @@ use_ref ref_container = use_ref(initial_value) -Returns a mutable :class:`~idom.core.hooks.Ref` object that has a single -:attr:`~idom.core.hooks.Ref.current` attribute that at first contains the -``initial_state``. The identity of the ``Ref`` object will be preserved for the lifetime -of the component. +Returns a mutable :class:`~idom.utils.Ref` object that has a single +:attr:`~idom.utils.Ref.current` attribute that at first contains the ``initial_state``. +The identity of the ``Ref`` object will be preserved for the lifetime of the component. A ``Ref`` is most useful if you need to incur side effects since updating its ``.current`` attribute doesn't trigger a re-render of the component. You'll often use this diff --git a/docs/source/roadmap.rst b/docs/source/roadmap.rst index f78425980..07166ef51 100644 --- a/docs/source/roadmap.rst +++ b/docs/source/roadmap.rst @@ -34,7 +34,7 @@ Roadmap they need purpose-built `compiler plugins `__ that will convert imports of React to point to the location ``react.js`` will be - once the component has been loaded via :class:`~idom.client.module.Module`. + once the component has been loaded via ``idom.client.module.Module`` Ideally developers of custom components should be able to operate in isolation without assuming anything about the environment they are running in. This has the diff --git a/noxfile.py b/noxfile.py index fb880cf24..9084feb2d 100644 --- a/noxfile.py +++ b/noxfile.py @@ -161,8 +161,18 @@ def test_docs(session: Session) -> None: """Verify that the docs build and that doctests pass""" install_requirements_file(session, "build-docs") install_idom_dev(session, extras="all") - session.run("sphinx-build", "-T", "-b", "html", "docs/source", "docs/build") - session.run("sphinx-build", "-T", "-b", "doctest", "docs/source", "docs/build") + session.run( + "sphinx-build", + "-a", # re-write all output files + "-T", # show full tracebacks + "-W", # turn warnings into errors + "--keep-going", # complete the build, but still report warnings as errors + "-b", + "html", + "docs/source", + "docs/build", + ) + session.run("sphinx-build", "-b", "doctest", "docs/source", "docs/build") @nox.session diff --git a/src/idom/config.py b/src/idom/config.py index c3ae8292e..19caaabcb 100644 --- a/src/idom/config.py +++ b/src/idom/config.py @@ -38,8 +38,8 @@ """The location IDOM will use to store its client application This directory **MUST** be treated as a black box. Downstream applications **MUST NOT** -assume anything about the structure of this directory see :mod:`idom.client.manage` for -a set of publically available APIs for working with the client. +assume anything about the structure of this directory see :mod:`idom.web.module` for a +set of publically available APIs for working with the client. """ IDOM_FEATURE_INDEX_AS_DEFAULT_KEY = _Option( diff --git a/src/idom/core/dispatcher.py b/src/idom/core/dispatcher.py index 9f6f8ade2..bf8932722 100644 --- a/src/idom/core/dispatcher.py +++ b/src/idom/core/dispatcher.py @@ -36,6 +36,7 @@ async def dispatch_single_view( send: SendCoroutine, recv: RecvCoroutine, ) -> None: + """Run a dispatch loop for a single view instance""" with layout: async with create_task_group() as task_group: task_group.start_soon(_single_outgoing_loop, layout, send) @@ -50,6 +51,7 @@ async def dispatch_single_view( async def create_shared_view_dispatcher( layout: Layout, run_forever: bool = False ) -> AsyncIterator[_SharedViewDispatcherFuture]: + """Enter a dispatch context where all subsequent view instances share the same state""" with layout: ( dispatch_shared_view, @@ -94,6 +96,7 @@ def dispatch_shared_view_soon( def ensure_shared_view_dispatcher_future( layout: Layout, ) -> Tuple[Future[None], SharedViewDispatcher]: + """Ensure the future of a dispatcher created by :func:`create_shared_view_dispatcher`""" dispatcher_future: Future[SharedViewDispatcher] = Future() async def dispatch_shared_view_forever() -> None: diff --git a/src/idom/core/layout.py b/src/idom/core/layout.py index bbae0a6bf..5023aa145 100644 --- a/src/idom/core/layout.py +++ b/src/idom/core/layout.py @@ -55,6 +55,7 @@ class LayoutEvent(NamedTuple): class Layout: + """Responsible for "rendering" components. That is, turning them into VDOM.""" __slots__ = [ "root", @@ -94,10 +95,12 @@ def __exit__(self, *exc: Any) -> None: return None def update(self, component: "AbstractComponent") -> None: + """Schedule a re-render of a component in the layout""" self._rendering_queue.put(component) return None async def dispatch(self, event: LayoutEvent) -> None: + """Dispatch an event to the targeted handler""" # It is possible for an element in the frontend to produce an event # associated with a backend model that has been deleted. We only handle # events if the element and the handler exist in the backend. Otherwise @@ -115,6 +118,7 @@ async def dispatch(self, event: LayoutEvent) -> None: ) async def render(self) -> LayoutUpdate: + """Await the next available render. This will block until a component is updated""" while True: component = await self._rendering_queue.get() if id(component) in self._model_state_by_component_id: diff --git a/src/idom/server/prefab.py b/src/idom/server/prefab.py index f11616611..33fb3a53a 100644 --- a/src/idom/server/prefab.py +++ b/src/idom/server/prefab.py @@ -80,9 +80,9 @@ def multiview_server( ) -> Tuple[MultiViewMount, Server[_App]]: """Set up a server where views can be dynamically added. - In other words this allows the user to work with IDOM in an imperative manner. - Under the hood this uses the :func:`idom.widgets.common.multiview` function to - add the views on the fly. + In other words this allows the user to work with IDOM in an imperative manner. Under + the hood this uses the :func:`idom.widgets.multiview` function to add the views on + the fly. Parameters: server: The server type to start up as a daemon @@ -93,8 +93,8 @@ def multiview_server( app: Optionally provide a prexisting application to register to Returns: - The server instance and a function for adding views. - See :func:`idom.widgets.common.multiview` for details. + The server instance and a function for adding views. See + :func:`idom.widgets.multiview` for details. """ mount, component = multiview() @@ -123,9 +123,9 @@ def hotswap_server( ) -> Tuple[MountFunc, Server[_App]]: """Set up a server where views can be dynamically swapped out. - In other words this allows the user to work with IDOM in an imperative manner. - Under the hood this uses the :func:`idom.widgets.common.hotswap` function to - swap the views on the fly. + In other words this allows the user to work with IDOM in an imperative manner. Under + the hood this uses the :func:`idom.widgets.hotswap` function to swap the views on + the fly. Parameters: server: The server type to start up as a daemon @@ -137,8 +137,8 @@ def hotswap_server( sync_views: Whether to update all displays with newly mounted components Returns: - The server instance and a function for swapping views. - See :func:`idom.widgets.common.hotswap` for details. + The server instance and a function for swapping views. See + :func:`idom.widgets.hotswap` for details. """ mount, component = hotswap(update_on_change=sync_views) diff --git a/src/idom/utils.py b/src/idom/utils.py index 4a392a0a4..93d1c7525 100644 --- a/src/idom/utils.py +++ b/src/idom/utils.py @@ -17,9 +17,6 @@ class Ref(Generic[_RefValue]): incur side effects. Generally refs should be avoided if possible, but sometimes they are required. - Attributes: - current: The present value. - Notes: You can compare the contents for two ``Ref`` objects using the ``==`` operator. """ @@ -28,6 +25,7 @@ class Ref(Generic[_RefValue]): def __init__(self, initial_value: _RefValue) -> None: self.current = initial_value + """The present value""" def set_current(self, new: _RefValue) -> _RefValue: """Set the current value and return what is now the old value diff --git a/src/idom/widgets.py b/src/idom/widgets.py index fe3922477..574aef314 100644 --- a/src/idom/widgets.py +++ b/src/idom/widgets.py @@ -190,7 +190,7 @@ def World(): Notebook where one might want multiple active views which can all be interacted with at the same time. - Refer to :func:`idom.server.imperative_server_mount` for a reference usage. + See :func:`idom.server.prefab.multiview_server` for a reference usage. """ views: Dict[str, ComponentConstructor] = {}