Skip to content

Commit 7c7d49f

Browse files
arman-ddljmao-denverchipkent
authored
Generalize Python InputTable class (deephaven#5236)
* Generalize Python InputTable class * Add async methods * Remove async methods * Improve col name getters Per jmao-denver Co-authored-by: Jianfeng Mao <4297243+jmao-denver@users.noreply.github.com> * Add tests; polish * Improve type hints and doc * Change getters to properties Per jmao Co-authored-by: Jianfeng Mao <4297243+jmao-denver@users.noreply.github.com> * Refactor property usage * Set the correct object type for InputTable * Polish pydoc Per Chip Co-authored-by: Chip Kent <5250374+chipkent@users.noreply.github.com> * Remove unnecessary jpy --------- Co-authored-by: Jianfeng Mao <4297243+jmao-denver@users.noreply.github.com> Co-authored-by: jianfengmao <jianfengmao@deephaven.io> Co-authored-by: Chip Kent <5250374+chipkent@users.noreply.github.com>
1 parent 7a6edb5 commit 7c7d49f

File tree

3 files changed

+66
-52
lines changed

3 files changed

+66
-52
lines changed

py/server/deephaven/table.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
from deephaven.updateby import UpdateByOperation
3131

3232
# Table
33-
_J_Table = jpy.get_type("io.deephaven.engine.table.Table")
33+
_JTable = jpy.get_type("io.deephaven.engine.table.Table")
3434
_JAttributeMap = jpy.get_type("io.deephaven.engine.table.AttributeMap")
3535
_JTableTools = jpy.get_type("io.deephaven.engine.util.TableTools")
3636
_JColumnName = jpy.get_type("io.deephaven.api.ColumnName")
@@ -426,7 +426,7 @@ class Table(JObjectWrapper):
426426
data ingestion operations, queries, aggregations, joins, etc.
427427
428428
"""
429-
j_object_type = _J_Table
429+
j_object_type = _JTable
430430

431431
def __init__(self, j_table: jpy.JType):
432432
self.j_table = jpy.cast(j_table, self.j_object_type)

py/server/deephaven/table_factory.py

+53-49
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,14 @@
1616
from deephaven.column import InputColumn, Column
1717
from deephaven.dtypes import DType, Duration, Instant
1818
from deephaven.execution_context import ExecutionContext
19-
from deephaven.jcompat import j_lambda
20-
from deephaven.jcompat import to_sequence
19+
from deephaven.jcompat import j_lambda, j_list_to_list, to_sequence
2120
from deephaven.table import Table
2221
from deephaven.update_graph import auto_locking_ctx
2322

2423
_JTableFactory = jpy.get_type("io.deephaven.engine.table.TableFactory")
2524
_JTableTools = jpy.get_type("io.deephaven.engine.util.TableTools")
2625
_JDynamicTableWriter = jpy.get_type("io.deephaven.engine.table.impl.util.DynamicTableWriter")
26+
_JBaseArrayBackedInputTable = jpy.get_type("io.deephaven.engine.table.impl.util.BaseArrayBackedInputTable")
2727
_JAppendOnlyArrayBackedInputTable = jpy.get_type(
2828
"io.deephaven.engine.table.impl.util.AppendOnlyArrayBackedInputTable")
2929
_JKeyedArrayBackedInputTable = jpy.get_type("io.deephaven.engine.table.impl.util.KeyedArrayBackedInputTable")
@@ -231,52 +231,20 @@ def write_row(self, *values: Any) -> None:
231231

232232

233233
class InputTable(Table):
234-
"""InputTable is a subclass of Table that allows the users to dynamically add/delete/modify data in it. There are two
235-
types of InputTable - append-only and keyed.
234+
"""InputTable is a subclass of Table that allows the users to dynamically add/delete/modify data in it.
236235
237-
The append-only input table is not keyed, all rows are added to the end of the table, and deletions and edits are
238-
not permitted.
239-
240-
The keyed input tablet has keys for each row and supports addition/deletion/modification of rows by the keys.
236+
Users should always create InputTables through factory methods rather than directly from the constructor.
241237
"""
238+
j_object_type = _JBaseArrayBackedInputTable
242239

243-
def __init__(self, col_defs: Dict[str, DType] = None, init_table: Table = None,
244-
key_cols: Union[str, Sequence[str]] = None):
245-
"""Creates an InputTable instance from either column definitions or initial table. When key columns are
246-
provided, the InputTable will be keyed, otherwise it will be append-only.
247-
248-
Args:
249-
col_defs (Dict[str, DType]): the column definitions
250-
init_table (Table): the initial table
251-
key_cols (Union[str, Sequence[str]): the name(s) of the key column(s)
252-
253-
Raises:
254-
DHError
255-
"""
256-
try:
257-
if col_defs is None and init_table is None:
258-
raise ValueError("either column definitions or init table should be provided.")
259-
elif col_defs and init_table:
260-
raise ValueError("both column definitions and init table are provided.")
261-
262-
if col_defs:
263-
j_arg_1 = _JTableDefinition.of(
264-
[Column(name=n, data_type=t).j_column_definition for n, t in col_defs.items()])
265-
else:
266-
j_arg_1 = init_table.j_table
267-
268-
key_cols = to_sequence(key_cols)
269-
if key_cols:
270-
super().__init__(_JKeyedArrayBackedInputTable.make(j_arg_1, key_cols))
271-
else:
272-
super().__init__(_JAppendOnlyArrayBackedInputTable.make(j_arg_1))
273-
self.j_input_table = self.j_table.getAttribute(_J_INPUT_TABLE_ATTRIBUTE)
274-
self.key_columns = key_cols
275-
except Exception as e:
276-
raise DHError(e, "failed to create a InputTable.") from e
240+
def __init__(self, j_table: jpy.JType):
241+
super().__init__(j_table)
242+
self.j_input_table = self.j_table.getAttribute(_J_INPUT_TABLE_ATTRIBUTE)
243+
if not self.j_input_table:
244+
raise DHError("the provided table input is not suitable for input tables.")
277245

278246
def add(self, table: Table) -> None:
279-
"""Writes rows from the provided table to this input table. If this is a keyed input table, added rows with keys
247+
"""Synchronously writes rows from the provided table to this input table. If this is a keyed input table, added rows with keys
280248
that match existing rows will replace those rows.
281249
282250
Args:
@@ -291,8 +259,8 @@ def add(self, table: Table) -> None:
291259
raise DHError(e, "add to InputTable failed.") from e
292260

293261
def delete(self, table: Table) -> None:
294-
"""Deletes the keys contained in the provided table from this keyed input table. If this method is called on an
295-
append-only input table, a PermissionError will be raised.
262+
"""Synchronously deletes the keys contained in the provided table from this keyed input table. If this method is called on an
263+
append-only input table, an error will be raised.
296264
297265
Args:
298266
table (Table): the table with the keys to delete
@@ -301,18 +269,33 @@ def delete(self, table: Table) -> None:
301269
DHError
302270
"""
303271
try:
304-
if not self.key_columns:
305-
raise PermissionError("deletion on an append-only input table is not allowed.")
306272
self.j_input_table.delete(table.j_table)
307273
except Exception as e:
308274
raise DHError(e, "delete data in the InputTable failed.") from e
309275

276+
@property
277+
def key_names(self) -> List[str]:
278+
"""The names of the key columns of the InputTable."""
279+
return j_list_to_list(self.j_input_table.getKeyNames())
280+
281+
@property
282+
def value_names(self) -> List[str]:
283+
"""The names of the value columns. By default, any column not marked as a key column is a value column."""
284+
return j_list_to_list(self.j_input_table.getValueNames())
285+
310286

311287
def input_table(col_defs: Dict[str, DType] = None, init_table: Table = None,
312288
key_cols: Union[str, Sequence[str]] = None) -> InputTable:
313-
"""Creates an InputTable from either column definitions or initial table. When key columns are
289+
"""Creates an in-memory InputTable from either column definitions or an initial table. When key columns are
314290
provided, the InputTable will be keyed, otherwise it will be append-only.
315291
292+
There are two types of in-memory InputTable - append-only and keyed.
293+
294+
The append-only input table is not keyed, all rows are added to the end of the table, and deletions and edits are
295+
not permitted.
296+
297+
The keyed input table has keys for each row and supports addition/deletion/modification of rows by the keys.
298+
316299
Args:
317300
col_defs (Dict[str, DType]): the column definitions
318301
init_table (Table): the initial table
@@ -324,7 +307,28 @@ def input_table(col_defs: Dict[str, DType] = None, init_table: Table = None,
324307
Raises:
325308
DHError
326309
"""
327-
return InputTable(col_defs=col_defs, init_table=init_table, key_cols=key_cols)
310+
311+
try:
312+
if col_defs is None and init_table is None:
313+
raise ValueError("either column definitions or init table should be provided.")
314+
elif col_defs and init_table:
315+
raise ValueError("both column definitions and init table are provided.")
316+
317+
if col_defs:
318+
j_arg_1 = _JTableDefinition.of(
319+
[Column(name=n, data_type=t).j_column_definition for n, t in col_defs.items()])
320+
else:
321+
j_arg_1 = init_table.j_table
322+
323+
key_cols = to_sequence(key_cols)
324+
if key_cols:
325+
j_table = _JKeyedArrayBackedInputTable.make(j_arg_1, key_cols)
326+
else:
327+
j_table = _JAppendOnlyArrayBackedInputTable.make(j_arg_1)
328+
except Exception as e:
329+
raise DHError(e, "failed to create an in-memory InputTable.") from e
330+
331+
return InputTable(j_table)
328332

329333

330334
def ring_table(parent: Table, capacity: int, initialize: bool = True) -> Table:

py/server/tests/test_table_factory.py

+11-1
Original file line numberDiff line numberDiff line change
@@ -327,24 +327,32 @@ def test_input_table(self):
327327
col_defs = {c.name: c.data_type for c in t.columns}
328328
with self.subTest("from table definition"):
329329
append_only_input_table = input_table(col_defs=col_defs)
330+
self.assertEqual(append_only_input_table.key_names, [])
331+
self.assertEqual(append_only_input_table.value_names, [col.name for col in cols])
330332
append_only_input_table.add(t)
331333
self.assertEqual(append_only_input_table.size, 2)
332334
append_only_input_table.add(t)
333335
self.assertEqual(append_only_input_table.size, 4)
334336

335337
keyed_input_table = input_table(col_defs=col_defs, key_cols="String")
338+
self.assertEqual(keyed_input_table.key_names, ["String"])
339+
self.assertEqual(keyed_input_table.value_names, [col.name for col in cols if col.name != "String"])
336340
keyed_input_table.add(t)
337341
self.assertEqual(keyed_input_table.size, 2)
338342
keyed_input_table.add(t)
339343
self.assertEqual(keyed_input_table.size, 2)
340344

341345
with self.subTest("from init table"):
342346
append_only_input_table = input_table(init_table=t)
347+
self.assertEqual(append_only_input_table.key_names, [])
348+
self.assertEqual(append_only_input_table.value_names, [col.name for col in cols])
343349
self.assertEqual(append_only_input_table.size, 2)
344350
append_only_input_table.add(t)
345351
self.assertEqual(append_only_input_table.size, 4)
346352

347353
keyed_input_table = input_table(init_table=t, key_cols="String")
354+
self.assertEqual(keyed_input_table.key_names, ["String"])
355+
self.assertEqual(keyed_input_table.value_names, [col.name for col in cols if col.name != "String"])
348356
self.assertEqual(keyed_input_table.size, 2)
349357
keyed_input_table.add(t)
350358
self.assertEqual(keyed_input_table.size, 2)
@@ -355,9 +363,11 @@ def test_input_table(self):
355363
append_only_input_table = input_table(init_table=t)
356364
with self.assertRaises(DHError) as cm:
357365
append_only_input_table.delete(t)
358-
self.assertIn("not allowed.", str(cm.exception))
366+
self.assertIn("doesn\'t support delete operation", str(cm.exception))
359367

360368
keyed_input_table = input_table(init_table=t, key_cols=["String", "Double"])
369+
self.assertEqual(keyed_input_table.key_names, ["String", "Double"])
370+
self.assertEqual(keyed_input_table.value_names, [col.name for col in cols if col.name != "String" and col.name != "Double"])
361371
self.assertEqual(keyed_input_table.size, 2)
362372
keyed_input_table.delete(t.select(["String", "Double"]))
363373
self.assertEqual(keyed_input_table.size, 0)

0 commit comments

Comments
 (0)