diff --git a/examples/commands.ipynb b/examples/commands.ipynb index 4202b10..1a24e86 100644 --- a/examples/commands.ipynb +++ b/examples/commands.ipynb @@ -98,7 +98,7 @@ " \"console:create\",\n", " insertMode=\"split-right\",\n", " kernelPreference={\"id\": app.kernelId},\n", - " transform=ipylab.TransformMode.connection, # Optional\n", + " transform=ipylab.Transform.connection, # Optional\n", ")" ] }, @@ -230,7 +230,7 @@ " execute=toggle_orientation,\n", " label=\"Swap orientation\",\n", " icon_class=\"jp-PythonIcon\",\n", - " frontend_transform=ipylab.TransformMode.raw,\n", + " frontend_transform=ipylab.Transform.raw,\n", ")" ] }, @@ -256,7 +256,7 @@ "metadata": {}, "outputs": [], "source": [ - "t = app.execute_command(cmd, transform=ipylab.TransformMode.raw, isToggleable=True)" + "t = app.execute_command(cmd, transform=ipylab.Transform.raw, isToggleable=True)" ] }, { @@ -320,7 +320,7 @@ "metadata": {}, "outputs": [], "source": [ - "t = app.commands.pallet.add(cmd, \"Python Commands\")" + "t = app.commands.pallet.add(cmd, \"All Python Commands\")" ] }, { @@ -563,9 +563,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Remove a command\n", - "\n", - "To remove a command that was previously added:" + "## Remove a command by name" ] }, { diff --git a/examples/generic.ipynb b/examples/generic.ipynb index 6ed1761..7860909 100644 --- a/examples/generic.ipynb +++ b/examples/generic.ipynb @@ -39,7 +39,7 @@ "\n", "## Making a connection\n", "\n", - "The easiest way to create a connection is to specify the the `transform` as a `TransformMode.connection`. Examples:\n", + "The easiest way to create a connection is to specify the the `transform` as a `Transform.connection`. Examples:\n", "\n", "* [Autostart](autostart.ipynb#Example-launching-a-small-app)\n", "* [Console](http://localhost:9999/lab/tree/commands.ipynb#Create-a-new-console)\n", diff --git a/examples/sessions.ipynb b/examples/sessions.ipynb index 87820d2..175d0b2 100644 --- a/examples/sessions.ipynb +++ b/examples/sessions.ipynb @@ -107,7 +107,7 @@ "metadata": {}, "outputs": [], "source": [ - "t = app.execute_command(\"notebook:create-new\", transform=ipylab.TransformMode.connection)" + "t = app.execute_command(\"notebook:create-new\", transform=ipylab.Transform.connection)" ] }, { @@ -154,8 +154,8 @@ "t = nb.get_attribute(\n", " \"\",\n", " transform={\n", - " \"transform\": ipylab.TransformMode.advanced,\n", - " \"mappings\": {\"context\": ipylab.TransformMode.connection, \"sessionContext\": ipylab.TransformMode.connection},\n", + " \"transform\": ipylab.Transform.advanced,\n", + " \"mappings\": {\"context\": ipylab.Transform.connection, \"sessionContext\": ipylab.Transform.connection},\n", " },\n", ")" ] diff --git a/ipylab/__init__.py b/ipylab/__init__.py index 7f180ba..6f70df7 100644 --- a/ipylab/__init__.py +++ b/ipylab/__init__.py @@ -9,25 +9,26 @@ "HasApp", "JupyterFrontEnd", "Connection", + "MainAreaConnection", "Panel", "SplitPanel", "Icon", "Area", "InsertMode", "hookimpl", - "TransformMode", + "Transform", "pack", "pack_code", "commands", ] -from ipylab.disposable_connection import Connection # noqa: I001 from ipylab import commands -from ipylab.asyncwidget import TransformMode, pack, pack_code +from ipylab.asyncwidget import pack, pack_code +from ipylab.common import Area, InsertMode, Transform +from ipylab.connection import Connection, MainAreaConnection from ipylab.hasapp import HasApp from ipylab.hookspecs import hookimpl from ipylab.jupyterfrontend import JupyterFrontEnd -from ipylab.shell import Area, InsertMode from ipylab.widgets import Icon, Panel, SplitPanel diff --git a/ipylab/asyncwidget.py b/ipylab/asyncwidget.py index 851c3ae..f5f3392 100644 --- a/ipylab/asyncwidget.py +++ b/ipylab/asyncwidget.py @@ -5,7 +5,6 @@ import asyncio import inspect import traceback -import typing import uuid from typing import TYPE_CHECKING, Any @@ -13,9 +12,7 @@ from traitlets import Container, Dict, Instance, Set, Unicode import ipylab._frontend as _fe -import ipylab.disposable_connection -from ipylab._compat.enum import StrEnum -from ipylab._compat.typing import NotRequired, TypedDict +from ipylab.common import JavascriptType, Transform, TransformType from ipylab.hasapp import HasApp from ipylab.hookspecs import pm @@ -44,126 +41,6 @@ def pack_code(code: str | inspect._SourceObjectType) -> str: return code -class TransformMode(StrEnum): - """The transformation to apply to the result of frontend operations prior to sending. - - - done: [default] A string '--DONE--' - - raw: No conversion. Note: data is serialized when sending, some object serialization will fail. - - function: Use a function to calculate the return value. ['code'] = 'function...' - - connection: Return a connection to a disposable object in the frontend. - By default the disposable will be closed when the kernel is shutdown. - To leave the disposable open use the setting `dispose_on_kernel_lost=False` when creating the connection. - - advanced: A mapping of keys to transformations to apply sequentially on the object. - - `function` - -------- - JS code defining a function and the data to return. - - The function must accept two args: obj, options. - - ``` - transform = { - "transform": TransformMode.function, - "code": "function (obj, options) { return obj.id; }", - } - - transform = { - "transform": TransformMode.connection, - "cid": "ID TO USE FOR CONNECTION", - "dispose_on_kernel_lost": True, # Optional Default is True - } - - `advanced` - --------- - ``` - transform = { - "transform": TransformMode.advanced, - "mappings": {path: TransformType, ...} - } - ``` - """ - - raw = "raw" - done = "done" - function = "function" - connection = "connection" - advanced = "advanced" - - @classmethod - def validate(cls, transform: TransformType): - """Return a valid copy of the transform.""" - if isinstance(transform, dict): - match cls(transform["transform"]): - case cls.function: - code = transform.get("code") - if not isinstance(code, str) or not code.startswith("function"): - raise TypeError - return TransformDictFunction(transform=TransformMode.function, code=code) - case cls.connection: - cid = transform.get("cid") - if not isinstance(cid, str): - raise TypeError - transform_ = TransformDictConnection(transform=TransformMode.connection, cid=cid) - if transform.get("dispose_on_kernel_lost") is False: - transform_["dispose_on_kernel_lost"] = False - return transform_ - case cls.advanced: - mappings = {} - transform_ = TransformDictAdvanced(transform=TransformMode.advanced, mappings=mappings) - mappings_ = transform.get("mappings") - if not isinstance(mappings_, dict): - raise TypeError - for pth, tfm in mappings_.items(): - mappings[pth] = cls.validate(tfm) - return transform_ - case _: - raise NotImplementedError - transform_ = TransformMode(transform) - if transform_ in [TransformMode.function, TransformMode.advanced]: - msg = "This type of transform should be passed as a dict to provide the additional arguments" - raise ValueError(msg) - return transform_ - - @classmethod - def transform_payload(cls, transform: TransformType, payload: dict): - """Transform the payload according to the transform.""" - transform_ = transform["transform"] if isinstance(transform, dict) else transform - match transform_: - case TransformMode.advanced: - mappings = typing.cast(TransformDictAdvanced, transform)["mappings"] - return {key: cls.transform_payload(mappings[key], payload[key]) for key in mappings} - case TransformMode.connection: - return ipylab.disposable_connection.Connection(**payload) - return payload - - -class TransformDictFunction(TypedDict): - transform: Literal[TransformMode.function] - code: NotRequired[str] - - -class TransformDictAdvanced(TypedDict): - transform: Literal[TransformMode.advanced] - mappings: dict[str, TransformDictAdvanced | TransformDictFunction | TransformDictConnection] - - -class TransformDictConnection(TypedDict): - transform: Literal[TransformMode.connection] - cid: str - dispose_on_kernel_lost: NotRequired[Literal[False]] # By default it will dispose when the kernel is lost. - - -TransformType = TransformMode | TransformDictAdvanced | TransformDictFunction | TransformDictConnection - - -class JavascriptType(StrEnum): - string = "string" - number = "number" - boolean = "boolean" - object = "object" - function = "function" - - class Response(asyncio.Event): def set(self, payload, error: Exception | None = None) -> None: if getattr(self, "_value", False): @@ -286,7 +163,7 @@ def _check_get_error(self, content: dict | None = None) -> IpylabFrontendError | if "cyclic" in error: msg += ( "\nNote: A cyclic error may be due a return value that cannot be converted to JSON. " - "Try changing the transform (eg: transform=ipylab.TransformMode.done)." + "Try changing the transform (eg: transform=ipylab.Transform.done)." ) else: msg += "\nNote: Additional information may be available in the browser console (press `F12`)" @@ -318,7 +195,7 @@ async def _send_receive(self, content: dict): async def _wait_response_check_error(self, response: Response, content: dict) -> Any: payload = await response.wait() - return TransformMode.transform_payload(content["transform"], payload) + return Transform.transform_payload(content["transform"], payload) def _on_frontend_msg(self, _, content: dict, buffers: list): error = self._check_get_error(content) @@ -374,7 +251,7 @@ def schedule_operation( self, operation: str, *, - transform: TransformType = TransformMode.raw, + transform: TransformType = Transform.raw, toLuminoWidget: Iterable[str] | None = None, **kwgs, ): @@ -383,9 +260,9 @@ def schedule_operation( operation: str Name corresponding to operation in JS frontend. - transform : TransformMode | dict + transform : Transform | dict The transform to apply to the result of the operation. - see: ipylab.TransformMode + see: ipylab.Transform toLuminoWidget: Iterable[str] | None A list of item name mappings to convert to a Lumino widget in the frontend. @@ -412,7 +289,7 @@ def schedule_operation( "ipylab_BE": ipylab_BE, "operation": operation, "kwgs": kwgs, - "transform": TransformMode.validate(transform), + "transform": Transform.validate(transform), } if toLuminoWidget: content["toLuminoWidget"] = list(map(str, toLuminoWidget)) @@ -422,7 +299,7 @@ def execute_method( self, path: str, *args, - transform: TransformType = TransformMode.raw, + transform: TransformType = Transform.raw, toLuminoWidget: Iterable[str] | None = None, **kwgs, ): @@ -452,7 +329,7 @@ def execute_method( **kwgs, ) - def get_attribute(self, path: str, *, transform: TransformType = TransformMode.raw, nullIfMissing=False): + def get_attribute(self, path: str, *, transform: TransformType = Transform.raw, nullIfMissing=False): """Obtain a serialized version of the attribute of the `base` object in the frontend. path: 'dotted.access.to.the.method' relative to base. @@ -467,9 +344,9 @@ def set_attribute( path: str, value, *, - value_transform: TransformType = TransformMode.raw, + value_transform: TransformType = Transform.raw, value_toLuminoWidget=False, - transform=TransformMode.done, + transform=Transform.done, ): """Set the attribute on the `path` of the `base` object in the Frontend. @@ -491,7 +368,7 @@ def set_attribute( "setAttribute", path=path, value=pack(value), - valueTransform=TransformMode.validate(value_transform), + valueTransform=Transform.validate(value_transform), toLuminoWidget=["value"] if value_toLuminoWidget else None, transform=transform, ) @@ -508,7 +385,7 @@ def update_values(self, path: str, values: dict, *, toLuminoWidget: Iterable[str eg. ["values.value_toLuminoWidget"] """ return self.schedule_operation( - "updateValues", path=path, values=values, transform=TransformMode.raw, toLuminoWidget=toLuminoWidget + "updateValues", path=path, values=values, transform=Transform.raw, toLuminoWidget=toLuminoWidget ) def list_attributes( @@ -518,7 +395,7 @@ def list_attributes( depth=2, *, how: Literal["names", "group", "raw"] = "group", - transform: TransformType = TransformMode.raw, + transform: TransformType = Transform.raw, skip_hidden=True, ) -> Task[dict | list]: """Get a mapping of attributes of the object at 'path' of the Frontend instance. diff --git a/ipylab/commands.py b/ipylab/commands.py index 2d9f97b..8a46112 100644 --- a/ipylab/commands.py +++ b/ipylab/commands.py @@ -10,8 +10,8 @@ from traitlets import Container, Instance, Tuple, Unicode, observe from ipylab._compat.typing import Any, NotRequired, TypedDict, Unpack -from ipylab.asyncwidget import AsyncWidgetBase, TransformMode, pack, register -from ipylab.disposable_connection import Connection +from ipylab.asyncwidget import AsyncWidgetBase, Transform, pack, register +from ipylab.connection import Connection if TYPE_CHECKING: from asyncio import Task @@ -63,13 +63,6 @@ async def configure_(): return self.to_task(configure_()) - def get_config(self) -> Task[CommandOptions]: - async def get_config_(): - config = await self.get_attribute("config", nullIfMissing=True) - return config or {} - - return self.to_task(get_config_()) - def add_launcher(self, category: str, rank=None, **args): """Add a launcher for this command. @@ -162,7 +155,7 @@ def add( "rank": rank, }, transform={ - "transform": TransformMode.connection, + "transform": Transform.connection, "cid": CommandPalletConnection.to_cid(str(command), category), }, ) @@ -195,7 +188,7 @@ def add(self, command: str | CommandConnection, category: str, *, rank=None, **a "args": args, }, transform={ - "transform": TransformMode.connection, + "transform": Transform.connection, "cid": LauncherConnection.to_cid(str(command), category), }, ) @@ -240,7 +233,7 @@ def add( label="", icon_class: str | None = None, icon: Icon | None = None, - frontend_transform: TransformType = TransformMode.done, + frontend_transform: TransformType = Transform.done, **kwgs, ) -> Task[CommandConnection]: """Add a python command that can be executed by Jupyterlab. @@ -261,9 +254,9 @@ def add( caption=caption, label=label, iconClass=icon_class, - transform={"transform": TransformMode.connection, "cid": cid}, + transform={"transform": Transform.connection, "cid": cid}, icon=pack(icon), - frontendTransform=TransformMode.validate(frontend_transform), + frontendTransform=Transform.validate(frontend_transform), **kwgs, ) diff --git a/ipylab/common.py b/ipylab/common.py new file mode 100644 index 0000000..59e4cb5 --- /dev/null +++ b/ipylab/common.py @@ -0,0 +1,162 @@ +from __future__ import annotations + +import typing +from typing import Literal + +import ipylab +from ipylab._compat.enum import StrEnum +from ipylab._compat.typing import NotRequired, TypedDict + +__all__ = ["Area", "InsertMode", "Transform", "TransformType", "JavascriptType"] + + +class Area(StrEnum): + # https://github.com/jupyterlab/jupyterlab/blob/da8e7bda5eebd22319f59e5abbaaa9917872a7e8/packages/application/src/shell.ts#L500 + main = "main" + left = "left" + right = "right" + header = "header" + top = "top" + bottom = "bottom" + down = "down" + menu = "menu" + + +class InsertMode(StrEnum): + # ref https://lumino.readthedocs.io/en/latest/api/types/widgets.DockLayout.InsertMode.html + split_top = "split-top" + split_left = "split-left" + split_right = "split-right" + split_bottom = "split-bottom" + merge_top = "merge-top" + merge_left = "merge-left" + merge_right = "merge-right" + merge_bottom = "merge-bottom" + tab_before = "tab-before" + tab_after = "tab-after" + + +class Transform(StrEnum): + """An eumeration of transformations than can be applied to serialized data. + + Data sent between the kernel and Frontend is serialized using JSON. The transform is used + to specify how that data should be transformed either prior to sending and/or once received. + + Transformations that require parameters should be specified in a dict with the the key 'transform' specifying + the transform, and other keys providing the parameters accordingly. + + - done: [default] A string '--DONE--' + - raw: No conversion. Note: data is serialized when sending, some object serialization will fail. + - function: Use a function to calculate the return value. ['code'] = 'function...' + - connection: Return a connection to a disposable object in the frontend. + By default the disposable will be closed when the kernel is shutdown. + To leave the disposable open use the setting `dispose_on_kernel_lost=False` when creating the connection. + - advanced: A mapping of keys to transformations to apply sequentially on the object. + + `function` + -------- + JS code defining a function and the data to return. + + The function must accept two args: obj, options. + + ``` + transform = { + "transform": Transform.function, + "code": "function (obj, options) { return obj.id; }", + } + + transform = { + "transform": Transform.connection, + "cid": "ID TO USE FOR CONNECTION", + "dispose_on_kernel_lost": True, # Optional Default is True + } + + `advanced` + --------- + ``` + transform = { + "transform": Transform.advanced, + "mappings": {path: TransformType, ...} + } + ``` + """ + + raw = "raw" + done = "done" + function = "function" + connection = "connection" + advanced = "advanced" + + @classmethod + def validate(cls, transform: TransformType): + """Return a valid copy of the transform.""" + if isinstance(transform, dict): + match cls(transform["transform"]): + case cls.function: + code = transform.get("code") + if not isinstance(code, str) or not code.startswith("function"): + raise TypeError + return TransformDictFunction(transform=Transform.function, code=code) + case cls.connection: + cid = transform.get("cid") + if not isinstance(cid, str): + raise TypeError + transform_ = TransformDictConnection(transform=Transform.connection, cid=cid) + if transform.get("dispose_on_kernel_lost") is False: + transform_["dispose_on_kernel_lost"] = False + return transform_ + case cls.advanced: + mappings = {} + transform_ = TransformDictAdvanced(transform=Transform.advanced, mappings=mappings) + mappings_ = transform.get("mappings") + if not isinstance(mappings_, dict): + raise TypeError + for pth, tfm in mappings_.items(): + mappings[pth] = cls.validate(tfm) + return transform_ + case _: + raise NotImplementedError + transform_ = Transform(transform) + if transform_ in [Transform.function, Transform.advanced]: + msg = "This type of transform should be passed as a dict to provide the additional arguments" + raise ValueError(msg) + return transform_ + + @classmethod + def transform_payload(cls, transform: TransformType, payload: dict): + """Transform the payload according to the transform.""" + transform_ = transform["transform"] if isinstance(transform, dict) else transform + match transform_: + case Transform.advanced: + mappings = typing.cast(TransformDictAdvanced, transform)["mappings"] + return {key: cls.transform_payload(mappings[key], payload[key]) for key in mappings} + case Transform.connection: + return ipylab.Connection(**payload) + return payload + + +class TransformDictFunction(TypedDict): + transform: Literal[Transform.function] + code: NotRequired[str] + + +class TransformDictAdvanced(TypedDict): + transform: Literal[Transform.advanced] + mappings: dict[str, TransformDictAdvanced | TransformDictFunction | TransformDictConnection] + + +class TransformDictConnection(TypedDict): + transform: Literal[Transform.connection] + cid: str + dispose_on_kernel_lost: NotRequired[Literal[False]] # By default it will dispose when the kernel is lost. + + +TransformType = Transform | TransformDictAdvanced | TransformDictFunction | TransformDictConnection + + +class JavascriptType(StrEnum): + string = "string" + number = "number" + boolean = "boolean" + object = "object" + function = "function" diff --git a/ipylab/disposable_connection.py b/ipylab/connection.py similarity index 80% rename from ipylab/disposable_connection.py rename to ipylab/connection.py index e9548a1..c679c51 100644 --- a/ipylab/disposable_connection.py +++ b/ipylab/connection.py @@ -18,9 +18,26 @@ class Connection(AsyncWidgetBase, Generic[T]): """A connection to an object in the Frontend. - This defines the 'base' as the object meaning the frontend attribute methods - are associated directly with the object on the frontend. - + Instances of `Connections` are created automatically when the transform is set as + `Transform.connection`. + + When the `cid` *prefix* matches a subclass `CID_PREFIX`, a new subclass instance will + be created in place of the Connection. + + If a specific subclass of Connection is required, the transform should be + specified using: + + ```python + transform = { + "transform": Transform.connection, + "cid": CONNECTION_SUBCLASS.new_cid(), + } + # or + transform = { + "transform": Transform.connection, + "cid": CONNECTION_SUBCLASS.to_cid(DETAILS), + } + ``` The 'dispose' method will call the dispose method on the frontend object and close this object. @@ -35,7 +52,7 @@ class Connection(AsyncWidgetBase, Generic[T]): will generate an appropriate id. """ - CID_PREFIX = "" + CID_PREFIX = "" # Required in subclassess to discriminate when creating. _CLASS_DEFINITIONS: dict[str, type[T]] = {} # noqa RUF012 _connections: dict[str, T] = {} # noqa RUF012 _model_name = Unicode("ConnectionModel").tag(sync=True) @@ -108,3 +125,11 @@ def get_existing_connection(cls, *name_or_id: str, quiet=False): msg = f"A connection does not exist with id='{cid}'" raise ValueError(msg) return conn + + +class MainAreaConnection(Connection): + CID_PREFIX = "ipylab MainArea" + + def activate(self): + self._check_closed() + return self.app.shell.execute_method("activateById", self.id) diff --git a/ipylab/jupyterfrontend.py b/ipylab/jupyterfrontend.py index 8c94f0d..71493b8 100644 --- a/ipylab/jupyterfrontend.py +++ b/ipylab/jupyterfrontend.py @@ -7,7 +7,7 @@ from traitlets import Dict, Instance, Tuple, Unicode -from ipylab.asyncwidget import AsyncWidgetBase, TransformMode, pack, pack_code, register, widget_serialization +from ipylab.asyncwidget import AsyncWidgetBase, Transform, pack, pack_code, register, widget_serialization from ipylab.commands import CommandRegistry from ipylab.dialog import Dialog, FileDialog from ipylab.hookspecs import pm @@ -64,7 +64,7 @@ def execute_command( self, command_id: str | CommandConnection, *, - transform: TransformType = TransformMode.done, + transform: TransformType = Transform.done, toLuminoWidget: Iterable[str] | None = None, **args, ): @@ -87,7 +87,7 @@ def exec_eval( execute: str | inspect._SourceObjectType, evaluate: dict[str, str], kernelId="", - frontend_transform: TransformType = TransformMode.done, + frontend_transform: TransformType = Transform.done, **kwgs, ): """Execute and evaluate code in the Python kernel with the id `kernelId`. @@ -115,7 +115,7 @@ def exec_eval( code = "import ipylab; ipylab.JupyterFrontEnd()" task = None if kernelId else self.session_manager.new_sessioncontext(code=code, **kwgs) - frontend_transform = TransformMode.validate(frontend_transform) + frontend_transform = Transform.validate(frontend_transform) async def exec_eval_(): k_id = kernelId @@ -152,4 +152,4 @@ async def _exec_eval(self, payload: dict, buffers: list) -> Any: def checkstart_iyplab_python_backend(self, *, restart=False): """Checks backend is running and starts it if it isn't, returning the session model.""" - return self.schedule_operation("startIyplabPythonBackend", restart=restart, transform=TransformMode.connection) + return self.schedule_operation("startIyplabPythonBackend", restart=restart, transform=Transform.connection) diff --git a/ipylab/sessions.py b/ipylab/sessions.py index 6c7fe37..242db3e 100644 --- a/ipylab/sessions.py +++ b/ipylab/sessions.py @@ -5,8 +5,8 @@ from types import ModuleType from typing import Literal -from ipylab.asyncwidget import AsyncWidgetBase, TransformMode, Unicode, pack_code -from ipylab.disposable_connection import Connection +from ipylab.asyncwidget import AsyncWidgetBase, Transform, Unicode, pack_code +from ipylab.connection import Connection class SessionManager(AsyncWidgetBase): @@ -56,5 +56,5 @@ def new_sessioncontext( kernelName=kernelName, type=type, code=pack_code(code), - transform=TransformMode.connection, + transform=Transform.connection, ) diff --git a/ipylab/shell.py b/ipylab/shell.py index 5ab4139..d2c9b37 100644 --- a/ipylab/shell.py +++ b/ipylab/shell.py @@ -4,44 +4,18 @@ import typing as t -from ipylab import pack -from ipylab._compat.enum import StrEnum -from ipylab.asyncwidget import AsyncWidgetBase, TransformMode, TransformType, Unicode +from ipylab import Area, MainAreaConnection, Transform, pack +from ipylab.asyncwidget import AsyncWidgetBase, Unicode +from ipylab.common import InsertMode if t.TYPE_CHECKING: from asyncio import Task from ipywidgets import Widget - from ipylab.disposable_connection import Connection + from ipylab.connection import Connection -__all__ = ["Area", "InsertMode", "Shell"] - - -class Area(StrEnum): - # https://github.com/jupyterlab/jupyterlab/blob/da8e7bda5eebd22319f59e5abbaaa9917872a7e8/packages/application/src/shell.ts#L500 - main = "main" - left = "left" - right = "right" - header = "header" - top = "top" - bottom = "bottom" - down = "down" - menu = "menu" - - -class InsertMode(StrEnum): - # ref https://lumino.readthedocs.io/en/latest/api/types/widgets.DockLayout.InsertMode.html - split_top = "split-top" - split_left = "split-left" - split_right = "split-right" - split_bottom = "split-bottom" - merge_top = "merge-top" - merge_left = "merge-left" - merge_right = "merge-right" - merge_bottom = "merge-bottom" - tab_before = "tab-before" - tab_after = "tab-after" +__all__ = ["Shell"] class Shell(AsyncWidgetBase): @@ -67,9 +41,8 @@ def add( mode: InsertMode = InsertMode.tab_after, rank: int | None = None, ref: Connection | None = None, - transform: TransformType = TransformMode.connection, **options, - ) -> Task[Connection]: + ) -> Task[MainAreaConnection]: """ Add the widget to the shell. @@ -89,7 +62,7 @@ def add( "addToShell", widget=pack(widget), area=Area(area), - transform=transform, + transform={"transform": Transform.connection, "cid": MainAreaConnection.new_cid()}, options=options_ | options, toLuminoWidget=["widget", "options.ref"], ) diff --git a/ipylab/widgets.py b/ipylab/widgets.py index 9c415c3..ee7f9f1 100644 --- a/ipylab/widgets.py +++ b/ipylab/widgets.py @@ -11,21 +11,15 @@ from traitlets import Dict, Instance, Unicode, observe import ipylab._frontend as _fe -from ipylab.asyncwidget import TransformMode, WidgetBase -from ipylab.disposable_connection import Connection +from ipylab.asyncwidget import WidgetBase +from ipylab.common import InsertMode from ipylab.hasapp import HasApp -from ipylab.shell import Area, InsertMode +from ipylab.shell import Area if TYPE_CHECKING: from asyncio import Task - -class MainAreaConnection(Connection): - CID_PREFIX = "ipylab MainArea" - - def activate(self): - self._check_closed() - return self.app.shell.execute_method("activateById", self.id) + from ipylab.connection import Connection, MainAreaConnection @register @@ -73,18 +67,9 @@ def add_to_shell( rank: int | None = None, ref: Connection | None = None, **options, - ) -> Task[Connection]: + ): """Add this panel to the shell.""" - return self.app.shell.add( - self, - area=area, - mode=mode, - activate=activate, - rank=rank, - ref=ref, - transform={"transform": TransformMode.connection, "cid": MainAreaConnection.new_cid()}, - **options, - ) + return self.app.shell.add(self, area=area, mode=mode, activate=activate, rank=rank, ref=ref, **options) @register @@ -124,7 +109,7 @@ def add_to_shell( rank: int | None = None, ref: Connection | None = None, **options, - ) -> Task[Connection]: + ) -> Task[MainAreaConnection]: task = super().add_to_shell(area=area, activate=activate, mode=mode, rank=rank, ref=ref, **options) async def _add_to_shell():