Skip to content

Commit 535153e

Browse files
committed
Transform objects on the ingress to data hooks.
1 parent b96112e commit 535153e

7 files changed

Lines changed: 147 additions & 14 deletions

File tree

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
from __future__ import annotations
2+
3+
from typing import Any, Optional, TypeVar, overload
4+
5+
from deephaven.execution_context import get_exec_ctx
6+
from deephaven.plugin_authorization import transform as _plugin_transform # type: ignore[import-not-found]
7+
8+
T = TypeVar("T")
9+
10+
11+
def _current_user_context() -> str:
12+
"""
13+
Return a string describing the current user's authorization context, for use in error messages.
14+
15+
Returns:
16+
A description of the current authorization context, or "unknown" if it cannot be determined.
17+
"""
18+
try:
19+
return str(get_exec_ctx().j_exec_ctx.getAuthContext())
20+
except Exception:
21+
return "unknown"
22+
23+
24+
@overload
25+
def transform(obj: None) -> None:
26+
...
27+
28+
29+
@overload
30+
def transform(obj: T) -> T:
31+
...
32+
33+
34+
def transform(obj: Optional[Any]) -> Optional[Any]:
35+
"""
36+
Apply the server's authorization transform to ``obj`` for the current user's context.
37+
38+
Unlike :func:`deephaven.plugin_authorization.transform`, this raises a clear error if the current user is not
39+
permitted to access a non-``None`` object, rather than silently returning ``None`` (which would otherwise surface
40+
later as a confusing ``AttributeError`` when the ``None`` result is used).
41+
42+
Args:
43+
obj: The object to transform (typically a table). ``None`` is returned unchanged.
44+
45+
Returns:
46+
The transformed object.
47+
48+
Raises:
49+
PermissionError: if the current user is not permitted to access ``obj``.
50+
"""
51+
if obj is None:
52+
return None
53+
result = _plugin_transform(obj)
54+
if result is None:
55+
raise PermissionError(
56+
f"The current user ({_current_user_context()}) is not permitted to access the requested object."
57+
)
58+
return result

plugins/ui/src/deephaven/ui/hooks/use_cell_data.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@
55

66
from deephaven.table import Table
77

8+
from ._transform import transform
89
from .use_memo import use_memo
9-
from .use_table_data import first_column_table, use_table_data
10+
from .use_table_data import first_column_table, _use_table_data_without_ticket_transform
1011
from ..types import Sentinel
1112

1213

@@ -42,7 +43,9 @@ def use_cell_data(table: Table | None, sentinel: Sentinel = None) -> Any | Senti
4243
Any: The top left cell of the table.
4344
"""
4445
filtered_table = use_memo(
45-
lambda: None if table is None else first_column_table(table).head(1),
46+
lambda: None if table is None else first_column_table(transform(table)).head(1),
4647
[table],
4748
)
48-
return use_table_data(filtered_table, sentinel, _cell_data)
49+
return _use_table_data_without_ticket_transform(
50+
filtered_table, sentinel, _cell_data
51+
)

plugins/ui/src/deephaven/ui/hooks/use_column_data.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@
44

55
from deephaven.table import Table
66

7+
from ._transform import transform
78
from .use_memo import use_memo
8-
from .use_table_data import first_column_table, use_table_data
9+
from .use_table_data import first_column_table, _use_table_data_without_ticket_transform
910
from ..types import Sentinel, ColumnData
1011

1112

@@ -43,7 +44,9 @@ def use_column_data(
4344
The first column of the table as a list or the sentinel value.
4445
"""
4546
filtered_table = use_memo(
46-
lambda: None if table is None else first_column_table(table),
47+
lambda: None if table is None else first_column_table(transform(table)),
4748
[table],
4849
)
49-
return use_table_data(filtered_table, sentinel, _column_data)
50+
return _use_table_data_without_ticket_transform(
51+
filtered_table, sentinel, _column_data
52+
)

plugins/ui/src/deephaven/ui/hooks/use_row_data.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@
44

55
from deephaven.table import Table
66

7+
from ._transform import transform
78
from .use_memo import use_memo
8-
from .use_table_data import use_table_data
9+
from .use_table_data import _use_table_data_without_ticket_transform
910
from ..types import Sentinel, RowData
1011

1112

@@ -42,5 +43,7 @@ def use_row_data(
4243
Returns:
4344
The first row of the table as a dictionary or the sentinel value.
4445
"""
45-
filtered_table = use_memo(lambda: None if table is None else table.head(1), [table])
46-
return use_table_data(filtered_table, sentinel, _row_data)
46+
filtered_table = use_memo(
47+
lambda: None if table is None else transform(table).head(1), [table]
48+
)
49+
return _use_table_data_without_ticket_transform(filtered_table, sentinel, _row_data)

plugins/ui/src/deephaven/ui/hooks/use_row_list.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@
55

66
from deephaven.table import Table
77

8+
from ._transform import transform
89
from .use_memo import use_memo
9-
from .use_table_data import use_table_data
10+
from .use_table_data import _use_table_data_without_ticket_transform
1011
from ..types import Sentinel
1112

1213

@@ -43,5 +44,7 @@ def use_row_list(
4344
Returns:
4445
The first row of the table as a list or the sentinel value.
4546
"""
46-
filtered_table = use_memo(lambda: None if table is None else table.head(1), [table])
47-
return use_table_data(filtered_table, sentinel, _row_list)
47+
filtered_table = use_memo(
48+
lambda: None if table is None else transform(table).head(1), [table]
49+
)
50+
return _use_table_data_without_ticket_transform(filtered_table, sentinel, _row_list)

plugins/ui/src/deephaven/ui/hooks/use_table_data.py

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,11 @@
1212
from deephaven.server.executors import submit_task
1313
from deephaven.update_graph import has_exclusive_lock
1414

15+
from ._transform import transform as apply_ticket_transform
1516
from .use_callback import use_callback
1617
from .use_effect import use_effect
1718
from .use_state import use_state
18-
from .use_table_listener import use_table_listener
19+
from .use_table_listener import _use_table_listener_without_ticket_transform
1920

2021
from ..types import Sentinel, TableData, TransformedData
2122

@@ -143,6 +144,34 @@ def use_table_data(
143144
Returns a dictionary with the contents of the table. Component will redraw if the table
144145
changes, resulting in an updated frame.
145146
147+
Args:
148+
table: The table to listen to. If None, None will be returned, not the sentinel value.
149+
sentinel: The sentinel value to return if the table is ticking but empty. Defaults to None.
150+
transform: A function to transform the table data and is_sentinel values. Defaults to None, which will
151+
return the data as TableData.
152+
153+
Returns:
154+
The table data or the sentinel value.
155+
"""
156+
table = apply_ticket_transform(table)
157+
return _use_table_data_without_ticket_transform(table, sentinel, transform)
158+
159+
160+
def _use_table_data_without_ticket_transform(
161+
table: Table | None,
162+
sentinel: Sentinel = None,
163+
transform: (
164+
Callable[[pd.DataFrame | Sentinel | None, bool], TransformedData | Sentinel]
165+
| None
166+
) = None,
167+
) -> TableData | Sentinel | TransformedData:
168+
"""
169+
Returns a dictionary with the contents of the table. Component will redraw if the table
170+
changes, resulting in an updated frame.
171+
172+
Note that plugin transformations are not applied, so this function is intended only for use by other hooks that
173+
have already applied a plugin transformation.
174+
146175
Args:
147176
table: The table to listen to. If None, None will be returned, not the sentinel value.
148177
sentinel: The sentinel value to return if the table is ticking but empty. Defaults to None.
@@ -181,6 +210,6 @@ def use_table_data(
181210
)
182211

183212
# call table_updated every time the table updates
184-
use_table_listener(table, listener, [])
213+
_use_table_listener_without_ticket_transform(table, listener, [])
185214

186215
return transform(data, is_sentinel)

plugins/ui/src/deephaven/ui/hooks/use_table_listener.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from deephaven.table_listener import listen, TableUpdate, TableListener
88
from deephaven.execution_context import get_exec_ctx, ExecutionContext
99

10+
from ._transform import transform
1011
from .use_effect import use_effect
1112
from ..types import LockType, Dependencies
1213

@@ -79,6 +80,39 @@ def use_table_listener(
7980
If any dependencies change, the listener will be recreated.
8081
In this case, updates may be missed if the table updates while the listener is being recreated.
8182
83+
Args:
84+
table: The table to listen to.
85+
listener: Either a function or a TableListener with an on_update function.
86+
The function must take a TableUpdate and is_replay bool.
87+
dependencies: Dependencies of the listener function, so the hook knows when to recreate the listener
88+
description: An optional description for the UpdatePerformanceTracker to append to the listener’s
89+
entry description, default is None.
90+
do_replay: Whether to replay the initial snapshot of the table, default is False.
91+
92+
Returns:
93+
None
94+
"""
95+
transformed = transform(table)
96+
return _use_table_listener_without_ticket_transform(
97+
transformed, listener, dependencies, description, do_replay
98+
)
99+
100+
101+
def _use_table_listener_without_ticket_transform(
102+
table: Table | None,
103+
listener: Callable[[TableUpdate, bool], None] | TableListener,
104+
dependencies: Dependencies,
105+
description: str | None = None,
106+
do_replay: bool = False,
107+
) -> None:
108+
"""
109+
Listen to a table and call a listener when the table updates.
110+
If any dependencies change, the listener will be recreated.
111+
In this case, updates may be missed if the table updates while the listener is being recreated.
112+
113+
Note that plugin transformations are not applied, so this function is intended only for use by other hooks that
114+
have already applied a plugin transformation.
115+
82116
Args:
83117
table: The table to listen to.
84118
listener: Either a function or a TableListener with an on_update function.

0 commit comments

Comments
 (0)