From 235a6a9d3e6970648e714f870f0444c91033b350 Mon Sep 17 00:00:00 2001 From: Marc Wouts Date: Mon, 27 May 2024 18:48:20 +0100 Subject: [PATCH] Document the Streamlit extension and reorganize the documentation --- README.md | 19 ++++++----- docs/_toc.yml | 23 +++++++++----- docs/advanced_parameters.md | 2 +- docs/contributing.md | 53 ++++++++++++++++++++++++++++--- docs/custom_css.md | 4 +-- docs/dash.md | 4 +++ docs/extensions.md | 7 +++- docs/formatting.md | 2 +- docs/html_export.md | 41 ++++++++++++++++++++++++ docs/ipywidgets.md | 4 +++ docs/pandas_style.md | 28 +++++++++------- docs/quarto.md | 14 -------- docs/quick_start.md | 31 +++++++++--------- docs/shiny.md | 14 ++++++++ docs/streamlit.md | 42 ++++++++++++++++++++++++ docs/supported_editors.md | 28 +--------------- docs/troubleshooting.md | 6 ++++ src/itables/javascript.py | 40 ++++++++++++++++++----- src/itables/options.py | 5 ++- src/itables/version.py | 2 +- tests/test_datatables_format.py | 1 + tests/test_extension_arguments.py | 10 ++++-- 22 files changed, 270 insertions(+), 110 deletions(-) create mode 100644 docs/dash.md create mode 100644 docs/html_export.md create mode 100644 docs/ipywidgets.md create mode 100644 docs/shiny.md create mode 100644 docs/streamlit.md diff --git a/README.md b/README.md index cfc36d64..5ba2de40 100644 --- a/README.md +++ b/README.md @@ -56,13 +56,12 @@ execute `init_notebook_mode`. ## Supported environments -`itables` has been tested in the following editors: -- Jupyter Notebook -- Jupyter Lab -- Jupyter nbconvert (i.e. the tables are still interactive in the HTML export of a notebook) -- Jupyter Book -- Google Colab -- VS Code (for both Jupyter Notebooks and Python scripts) -- PyCharm (for Jupyter Notebooks) -- Quarto -- Shiny for Python +ITables works in all the usual Jupyter Notebook environments, including Jupyter Notebook, Jupyter Lab, Jupyter nbconvert (i.e. the tables are still interactive in the HTML export of a notebook), Jupyter Book, Google Colab and Kaggle. + +You can also use ITables in [Quarto](https://mwouts.github.io/itables/quarto.html) HTML documents, and in RISE presentations. + +ITables works well in VS Code, both in Jupyter Notebooks and in interactive Python sessions. + +Last but not least, ITables is also available in +[Streamlit](https://mwouts.github.io/itables/streamlit.html) or +[Shiny](https://mwouts.github.io/itables/shiny.html) applications. diff --git a/docs/_toc.yml b/docs/_toc.yml index 1df44b5b..54f5a1b7 100644 --- a/docs/_toc.yml +++ b/docs/_toc.yml @@ -1,19 +1,27 @@ format: jb-book root: quick_start parts: - - caption: How to use DataTables + - caption: ITables in Notebooks chapters: - file: advanced_parameters - - file: formatting - file: custom_css + - file: formatting + - file: pandas_style - file: extensions - file: custom_extensions - - caption: ITables + - file: quarto + - file: downsampling + - file: references + - file: supported_editors + - caption: ITables in Applications + chapters: + - file: html_export + - file: dash + - file: shiny + - file: streamlit + - file: ipywidgets + - caption: Contributing to ITables chapters: - - file: supported_editors - - file: quarto - - file: downsampling - - file: references - file: contributing - file: developing - file: troubleshooting @@ -22,4 +30,3 @@ parts: chapters: - file: sample_dataframes - file: polars_dataframes - - file: pandas_style diff --git a/docs/advanced_parameters.md b/docs/advanced_parameters.md index 5191aa3c..e490c7ee 100644 --- a/docs/advanced_parameters.md +++ b/docs/advanced_parameters.md @@ -12,7 +12,7 @@ kernelspec: name: itables --- -# The DataTables Arguments +# Advanced Parameters ITables is a wrapper for the Javascript [DataTables](https://datatables.net/) library, which has a great [documentation](https://datatables.net/), a huge collection of [examples](https://datatables.net/examples/index), and a useful [forum](https://datatables.net/forums/). diff --git a/docs/contributing.md b/docs/contributing.md index 46b93589..cb990ad8 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -1,16 +1,30 @@ +--- +jupytext: + formats: md:myst + notebook_metadata_filter: -jupytext.text_representation.jupytext_version + text_representation: + extension: .md + format_name: myst + format_version: 0.13 +kernelspec: + display_name: itables + language: python + name: itables +--- + # Contributing Thanks for considering making a contribution to ITables. There are -indeed many ways to help! +many ways you can help! -## Reporting an issue +## Report an issue If you see an issue, a possible improvement, or if you can't find the answer to your question, then you are very welcome to create an issue on this project. Please provide enough detail so that we can reproduce the issue. -## Improving the documentation +## Improve the documentation If you would like to add a new example, or improve the documentation, feel free to make a pull request! @@ -18,12 +32,41 @@ or improve the documentation, feel free to make a pull request! You can render the documentation locally - see the section on [Jupyter Book](developing.md#jupyter-book) in the developer guide. -## Developing a new feature +## Give credit to ITables + +It's always great to see new stars coming to ITables! + + +If you wanted to share a link to ITables and DataTables (no obligation whatsoever), you could use something like this: + +```{code-cell} +from IPython.display import HTML, display + +display( + HTML( + """ +Tables displayed with ITables, +a Python wrapper for DataTables +""" + ) +) +``` + +## Support DataTables + +Allan Jardine, the main developer of DataTables, has done a fantastic work on [DataTables](https://datatables.net/). + +If you enjoy his library, you could become a +[supporter](https://datatables.net/supporters/) - +contributions range from 9 to 99$/year before VAT. +Or you could take a subscription for DataTable's [Editor](https://editor.datatables.net) +that ITables might support in the future (please subscribe to [#243](https://github.com/mwouts/itables/issues/243) for updates). + +## Develop a new feature It is generally a good idea to get in touch with us first - e.g. open an issue and let us know what you'd like to do. But you can also simply clone the project and test your ideas. - A guide on how to set up a development environment, and how to run some tests, is available [here](developing.md). diff --git a/docs/custom_css.md b/docs/custom_css.md index 57ec1247..05323d1d 100644 --- a/docs/custom_css.md +++ b/docs/custom_css.md @@ -12,7 +12,7 @@ kernelspec: name: itables --- -# Styling +# Table Style and CSS As usual, we initialize ITables with `init_notebook_mode`, and we create two sample DataFrames: @@ -113,7 +113,7 @@ show(df, classes="display nowrap table_with_monospace_font") The `show` function has a `style` argument that determines the style for that particular table. -The default value for `style` is `table-layout:auto;width:auto;margin:auto;caption-side:bottom`. +The default value for `style` is `"table-layout:auto;width:auto;margin:auto;caption-side:bottom"`. When `scrollX` is `True`, `margin:auto` gets replaced with `margin:0` to avoid misaligned headers. Without `width:auto`, tables with few columns still take the full notebook width in Jupyter. Using `margin:auto` makes non-wide tables centered in Jupyter. diff --git a/docs/dash.md b/docs/dash.md new file mode 100644 index 00000000..32d60854 --- /dev/null +++ b/docs/dash.md @@ -0,0 +1,4 @@ +# Dash + +ITables does not have a [Dash](https://dash.plotly.com/) component, but might get one in the future. +You are welcome to subscribe or contribute to [#245](https://github.com/mwouts/itables/issues/245). diff --git a/docs/extensions.md b/docs/extensions.md index efd73424..459f25ac 100644 --- a/docs/extensions.md +++ b/docs/extensions.md @@ -12,7 +12,7 @@ kernelspec: name: itables --- -# DataTables Extensions +# Download Buttons and Other Extensions DataTables comes with a series of [extensions](https://datatables.net/extensions/), which are supported by ITables since v2.0. A selection of these extensions are included in ITables. @@ -102,6 +102,11 @@ show( ) ``` +```{warning} +At the moment, the CSV and Excel buttons don't work well with large tables in some browsers. +Please subscribe to [#251](https://github.com/mwouts/itables/issues/251) if you wish to receive updates on this. +``` + ```{warning} The PDF button is not included in ITables' DataTable bundle. This is because the required PDF libraries have a large footprint on the bundle size. Still, you can add it to your custom bundle, see the next chapter. diff --git a/docs/formatting.md b/docs/formatting.md index 78e39834..2ffba820 100644 --- a/docs/formatting.md +++ b/docs/formatting.md @@ -12,7 +12,7 @@ kernelspec: name: itables --- -# Formatting +# Cell Formatting ## Formatting with Pandas diff --git a/docs/html_export.md b/docs/html_export.md new file mode 100644 index 00000000..b2eec255 --- /dev/null +++ b/docs/html_export.md @@ -0,0 +1,41 @@ +--- +jupytext: + formats: md:myst + notebook_metadata_filter: -jupytext.text_representation.jupytext_version + text_representation: + extension: .md + format_name: myst + format_version: 0.13 +kernelspec: + display_name: itables + language: python + name: itables +--- + +# HTML export + +To get the HTML representation of a Pandas DataFrame `df` as an interactive [DataTable](https://datatables.net/), you can use `to_html_datatable` as below: + +```{code-cell} +from IPython.display import HTML, display + +from itables import to_html_datatable +from itables.sample_dfs import get_countries + +df = get_countries(html=False) +html = to_html_datatable(df.head(3), display_logo_when_loading=False) +``` + +You can then save the `html` variable to a text file (note: if you're writing an HTML application, you could consider using [Shiny](shiny.md) or [Streamlit](streamlit.md) instead), or print it: + +```{code-cell} +:tags: [scroll-output] + +print(html) +``` + +or display it, like `show` does: + +```{code-cell} +display(HTML(html)) +``` diff --git a/docs/ipywidgets.md b/docs/ipywidgets.md new file mode 100644 index 00000000..23238906 --- /dev/null +++ b/docs/ipywidgets.md @@ -0,0 +1,4 @@ +# IPyWidgets + +ITables does not come as a [Jupyter Widget](https://ipywidgets.readthedocs.io) at the moment. +You are welcome to subscribe or contribute to [#267](https://github.com/mwouts/itables/issues/267). diff --git a/docs/pandas_style.md b/docs/pandas_style.md index d15f6e7b..81f5c8e2 100644 --- a/docs/pandas_style.md +++ b/docs/pandas_style.md @@ -12,11 +12,26 @@ kernelspec: name: itables --- -# Pandas Style examples +# Using Pandas Style Starting with `itables>=1.6.0`, ITables provides support for [Pandas Style](https://pandas.pydata.org/docs/user_guide/style.html). +```{note} +Unlike Pandas or Polar DataFrames, `Styler` objects are rendered directly using their +`to_html` method, rather than passing the underlying table data to the DataTables +library. + +Because of this, the rendering of the table might differ slightly from the rendering of the +corresponding DataFrame. In particular, +- The downsampling is not available - please pay attention to the size of the table being rendered +- Sorting of numbers will not work if the column contains NaNs. +``` + +```{warning} +Pandas Style objects can't be used with the [Streamlit extension](streamlit.md) for ITables. +``` + ```{code-cell} import numpy as np import pandas as pd @@ -83,14 +98,3 @@ ttips = pd.DataFrame( ) s.set_tooltips(ttips).set_caption("With tooltips") ``` - -```{note} -Unlike Pandas or Polar DataFrames, `Styler` objects are rendered directly using their -`to_html` method, rather than passing the underlying table data to the DataTables -library. - -Because of this, the rendering of the table might differ slightly from the rendering of the -corresponding DataFrame. In particular, -- The downsampling is not available - please pay attention to the size of the table being rendered -- Sorting of numbers will not work if the column contains NaNs. -``` diff --git a/docs/quarto.md b/docs/quarto.md index ec0af060..da5c3148 100644 --- a/docs/quarto.md +++ b/docs/quarto.md @@ -1,17 +1,3 @@ ---- -jupytext: - formats: md:myst - notebook_metadata_filter: -jupytext.text_representation.jupytext_version - text_representation: - extension: .md - format_name: myst - format_version: 0.13 -kernelspec: - display_name: itables - language: python - name: itables ---- - # Quarto ITables works well with the `revealjs` and `html` outputs formats of [Quarto](https://quarto.org). diff --git a/docs/quick_start.md b/docs/quick_start.md index c8889d7e..296247eb 100644 --- a/docs/quick_start.md +++ b/docs/quick_start.md @@ -20,8 +20,8 @@ kernelspec: [![Conda Version](https://img.shields.io/conda/vn/conda-forge/itables.svg)](https://anaconda.org/conda-forge/itables) [![pyversions](https://img.shields.io/pypi/pyversions/itables.svg)](https://pypi.python.org/pypi/itables) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) -Star - + + This packages changes how Pandas and Polars DataFrames are rendered in Jupyter Notebooks. With `itables` you can display your tables as interactive [DataTables](https://datatables.net/) @@ -49,9 +49,9 @@ or conda install itables -c conda-forge ``` -## Activate ITables +## Activate ITables in a Notebook -Activate the interactive mode for all series and dataframes with +Activate the interactive mode in notebook-like environment with `init_notebook_mode`: ```{code-cell} from itables import init_notebook_mode @@ -70,6 +70,16 @@ df = get_countries(html=False) df ``` +If you prefer to render only certain tables using `itables`, or want +to pass [advanced parameters](advanced_parameters.md) along with the +DataFrame, use `show`: + +```{code-cell} +from itables import show + +show(df, lengthMenu=[2, 5, 10, 25, 50, 100, 250]) +``` + ## Offline mode versus connected mode ITables use two Javascript libraries: @@ -85,17 +95,6 @@ To do so, add the argument `connected=True` when you execute `init_notebook_mode`. This will also make your notebook lighter by about [700kB](https://github.com/mwouts/itables/blob/main/tests/test_connected_notebook_is_small.py). -## Using ITables for specific tables only - -If you prefer to render only certain series or dataframes using `itables`, -then call `init_notebook_mode(all_interactive=False)` then `show`: - -```{code-cell} -from itables import show - -show(df, lengthMenu=[2, 5, 10, 25, 50, 100, 250]) -``` - ## Try ITables on Binder -You can run our examples notebooks directly on [![Lab](https://img.shields.io/badge/Binder-JupyterLab-blue.svg)](https://mybinder.org/v2/gh/mwouts/itables/main?urlpath=lab/tree/docs/quick_start.md), without having to install anything on your side. +You can run the examples above (or any other documentation page) directly on ![Lab](https://img.shields.io/badge/Binder-JupyterLab-blue.svg), without having to install anything on your end - just click on the rocket icon at the top of the page. diff --git a/docs/shiny.md b/docs/shiny.md new file mode 100644 index 00000000..6fe0bf07 --- /dev/null +++ b/docs/shiny.md @@ -0,0 +1,14 @@ +# Shiny + +You can use ITables in Web applications generated with [Shiny](https://shiny.rstudio.com/py/) for Python with e.g. +```python +from shiny import ui + +from itables.sample_dfs import get_countries +from itables.shiny import DT + +df = get_countries(html=False) +ui.HTML(DT(df)) +``` + +See also our [tested examples](https://github.com/mwouts/itables/tree/main/tests/sample_python_apps). diff --git a/docs/streamlit.md b/docs/streamlit.md new file mode 100644 index 00000000..9958fc62 --- /dev/null +++ b/docs/streamlit.md @@ -0,0 +1,42 @@ +# Streamlit + +ITables in version `2.1.0` or above can be used in Streamlit. + +To render a DataFrame with ITables in a Streamlit app, use `interactive_table`: +``` +from itables.streamlit import interactive_table +``` + +The function `interactive_table` accepts the same arguments as `show` and `to_html_datatable`, e.g. the +first argument is the dataframe that will be displayed, and then you +can set a `caption`, custom `classes` or `style`, and even activate the `buttons` extension, etc... + +Unlike `show`, `interactive_table` has `scrollX=True` by default. This makes the +rendering of wide tables more similar to that of `show` in Notebooks. + +## A sample application + +A sample Streamlit application is available at [itables.streamlit.app](https://itables.streamlit.app) (source code [here](https://github.com/mwouts/demo_itables_in_streamlit/blob/main/itables_app.py)) + + + +## Limitations of ITables in Streamlit + +From a user perspective, you will be able to use `interactive_table` in a +Streamlit application in the same way that you use `show` in notebooks. + +Due to implementation constraints, the Streamlit component has some limitations +that `show` does not have: +- Pandas Style objects can't be rendered with `interactive_table`. This is because +the Streamlit component needs to pass the table data to the frontend in JSON format (while Pandas Style objects are formatted using HTML) +- Similarly, you can't use the `use_to_html` argument in `interactive_table` +- Complex column headers might look different than in notebooks, and HTML in columns is not supported +- JavaScript callbacks like custom formatting functions are not supported +- The interactive table is rendered within an iframe that has a fixed weight. This does not work well with the `lengthMenu` control, nor with the advanced filtering extensions (if that is an issue for you, please subscribe or contribute to [#275](https://github.com/mwouts/itables/issues/275)). + +## Future developments + +ITables' Streamlit component might see the following developments in the future +- Return the selected cells +- Make the table editable (will require a DataTable [editor license](https://editor.datatables.net/purchase/)) diff --git a/docs/supported_editors.md b/docs/supported_editors.md index 4b4e8077..c20fe880 100644 --- a/docs/supported_editors.md +++ b/docs/supported_editors.md @@ -1,6 +1,6 @@ # Supported Editors -`itables` has been tested in many development environments. +ITables has been tested in the following environments ## Jupyter Notebook @@ -48,29 +48,3 @@ because otherwise the notebooks do not display the interactive tables when they ITables works well with Quarto - check out our `html` and `revealjs` [examples](quarto.md). - -# Exporting a DataFrame to an HTML table - -To get the HTML representation of a Pandas DataFrame `df` as an interactive [DataTable](https://datatables.net/), you can use `to_html_datatable` as below: -```python -from itables import to_html_datatable -from itables.sample_dfs import get_countries - -df = get_countries() -html = to_html_datatable(df) -``` - -# Using ITables in Shiny - -You can use ITables in Web applications generated with [Shiny](https://shiny.rstudio.com/py/) for Python with e.g. -```python -from shiny import ui - -from itables.shiny import DT - -ui.HTML(DT(df)) -``` - -See also our [tested examples](https://github.com/mwouts/itables/tree/main/tests/sample_python_apps). - -ITables won't work in Dash because jQuery is not usable in Dash (see the [Dash FAQ](https://dash.plotly.com/faqs)). diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index ecc0f723..862d84ca 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -14,6 +14,8 @@ kernelspec: # Troubleshooting +## Loading takes forever? + ```{code-cell} :tags: [hide-input] @@ -40,6 +42,8 @@ the `init_notebook_mode` cell, you need to re-execute all the cells that display interactive tables. ``` +## Trust your notebook + It could also be that your notebook is not _trusted_. This happens when you have not run the notebook in full yourself (e.g. the notebook was sent to you with outputs, or the notebook was created by a tool like `papermill`). In that case, JavaScript @@ -47,6 +51,8 @@ code cannot run (and the interactive tables won't display) until you tell Jupyter that you trust the notebook content (run "Trust Notebook" in View / Activate Command Palette). +## Check ITables' version + If the above does not help, please check out the [ChangeLog](changelog.md) and decide whether you should upgrade `itables`. You can tell the version of ITables that you are using by looking at the loading message (from ITables v2.0.1 on) diff --git a/src/itables/javascript.py b/src/itables/javascript.py index 86f8a2b3..f80a0f0c 100644 --- a/src/itables/javascript.py +++ b/src/itables/javascript.py @@ -117,8 +117,8 @@ def init_notebook_mode( display(HTML(generate_init_offline_itables_html(dt_bundle))) -def get_animated_logo(): - if not opt.display_logo_when_loading: +def get_animated_logo(display_logo_when_loading): + if not display_logo_when_loading: return "" return f"""
{read_package_file("logo/loading.svg")} @@ -136,7 +136,7 @@ def generate_init_offline_itables_html(dt_bundle: Path): return f"""
-{get_animated_logo()}
+{get_animated_logo(opt.display_logo_when_loading)}
This is the init_notebook_mode cell from ITables v{itables_version}
(you should not see this message - is your notebook trusted?)
@@ -149,7 +149,16 @@ def generate_init_offline_itables_html(dt_bundle: Path): def _table_header( - df, table_id, show_index, classes, style, tags, footer, column_filters, connected + df, + table_id, + show_index, + classes, + style, + tags, + footer, + column_filters, + connected, + display_logo_when_loading, ): """This function returns the HTML table header. Rows are not included.""" # Generate table head using pandas.to_html(), see issue 63 @@ -170,7 +179,7 @@ def _table_header( ) tbody = f""" -{get_animated_logo()}
+{get_animated_logo(display_logo_when_loading)}
Loading ITables v{itables_version} from {itables_source}... (need help?)
@@ -402,7 +411,16 @@ def to_html_datatable( pass table_header = _table_header( - df, tableId, showIndex, classes, style, tags, footer, column_filters, connected + df, + tableId, + showIndex, + classes, + style, + tags, + footer, + column_filters, + connected=connected, + display_logo_when_loading=kwargs.pop("display_logo_when_loading"), ) # Export the table data to JSON and include this in the HTML @@ -443,7 +461,7 @@ def _raise_if_javascript_code(values, context=""): return -def get_itables_extension_arguments(df, caption=None, **kwargs): +def get_itables_extension_arguments(df, caption=None, scrollX=True, **kwargs): """ This function returns two dictionaries that are JSON serializable and can be passed to the itables extensions. @@ -457,6 +475,8 @@ def get_itables_extension_arguments(df, caption=None, **kwargs): "Pandas style objects can't be used with the extension" ) + kwargs["scrollX"] = scrollX + set_default_options( kwargs, use_to_html=False, @@ -468,6 +488,7 @@ def get_itables_extension_arguments(df, caption=None, **kwargs): "use_to_html", "footer", "column_filters", + "display_logo_when_loading", ], ) @@ -596,12 +617,15 @@ def set_default_options(kwargs, use_to_html, context=None, not_available=()): not in { "dt_bundle", "find_package_file", - "display_logo_when_loading", "UNPKG_DT_BUNDLE_URL", } ): kwargs[option] = getattr(opt, option) + if kwargs.get("scrollX", False): + # column headers are misaligned if we let margin:auto + kwargs["style"] = kwargs["style"].replace(";margin:auto;", ";margin:0;") + for name, value in kwargs.items(): if value is None: raise ValueError( diff --git a/src/itables/options.py b/src/itables/options.py index 9c7183b0..36faed6b 100644 --- a/src/itables/options.py +++ b/src/itables/options.py @@ -26,7 +26,10 @@ - 'table-layout:auto' to compute the layout automatically - 'width:auto' to fit the table width to its content - 'margin:auto' to center the table. -Combine multiple options using ';'.""" +Combine multiple options using ';'. + +NB: When scrollX=true, the default is changed to "margin:0;caption-side:bottom" +""" style = "table-layout:auto;width:auto;margin:auto;caption-side:bottom" """Additional tags like e.g. caption""" diff --git a/src/itables/version.py b/src/itables/version.py index c46d31de..8afee4bc 100644 --- a/src/itables/version.py +++ b/src/itables/version.py @@ -1,3 +1,3 @@ """ITables' version number""" -__version__ = "2.1.0rc2" +__version__ = "2.1.0rc3" diff --git a/tests/test_datatables_format.py b/tests/test_datatables_format.py index 251a65c0..81c003e0 100644 --- a/tests/test_datatables_format.py +++ b/tests/test_datatables_format.py @@ -112,6 +112,7 @@ def test_datatables_rows(df, expected): footer=False, column_filters=False, connected=False, + display_logo_when_loading=False, ) column_count = _column_count_in_header(table_header) actual = datatables_rows(df, count=column_count) diff --git a/tests/test_extension_arguments.py b/tests/test_extension_arguments.py index 5bf8beb4..7f78f86a 100644 --- a/tests/test_extension_arguments.py +++ b/tests/test_extension_arguments.py @@ -11,9 +11,13 @@ def test_get_itables_extension_arguments(df): except NotImplementedError as e: pytest.skip(str(e)) - assert set(ext_args["dt_args"]) <= {"data", "columns", "layout", "order"}, set( - ext_args["dt_args"] - ) + assert set(ext_args["dt_args"]) <= { + "data", + "columns", + "layout", + "order", + "scrollX", + }, set(ext_args["dt_args"]) assert isinstance(ext_args["dt_args"]["data"], list) assert isinstance(ext_args["dt_args"]["columns"], list)