Skip to content

Commit

Permalink
Renamed TransformMode to Transform and other refactoring. Added ipyla…
Browse files Browse the repository at this point in the history
…b.common.py
  • Loading branch information
Alan Fleming committed Sep 2, 2024
1 parent 0d15874 commit f6a4637
Show file tree
Hide file tree
Showing 12 changed files with 248 additions and 234 deletions.
12 changes: 5 additions & 7 deletions examples/commands.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -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",
")"
]
},
Expand Down Expand Up @@ -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",
")"
]
},
Expand All @@ -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)"
]
},
{
Expand Down Expand Up @@ -320,7 +320,7 @@
"metadata": {},
"outputs": [],
"source": [
"t = app.commands.pallet.add(cmd, \"Python Commands\")"
"t = app.commands.pallet.add(cmd, \"All Python Commands\")"
]
},
{
Expand Down Expand Up @@ -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"
]
},
{
Expand Down
2 changes: 1 addition & 1 deletion examples/generic.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
6 changes: 3 additions & 3 deletions examples/sessions.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -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)"
]
},
{
Expand Down Expand Up @@ -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",
")"
]
Expand Down
9 changes: 5 additions & 4 deletions ipylab/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down
151 changes: 14 additions & 137 deletions ipylab/asyncwidget.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,14 @@
import asyncio
import inspect
import traceback
import typing
import uuid
from typing import TYPE_CHECKING, Any

from ipywidgets import Widget, register, widget_serialization
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

Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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`)"
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -374,7 +251,7 @@ def schedule_operation(
self,
operation: str,
*,
transform: TransformType = TransformMode.raw,
transform: TransformType = Transform.raw,
toLuminoWidget: Iterable[str] | None = None,
**kwgs,
):
Expand All @@ -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.
Expand All @@ -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))
Expand All @@ -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,
):
Expand Down Expand Up @@ -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.
Expand All @@ -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.
Expand All @@ -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,
)
Expand All @@ -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(
Expand All @@ -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.
Expand Down
Loading

0 comments on commit f6a4637

Please sign in to comment.