-
Notifications
You must be signed in to change notification settings - Fork 63
refactor: Migrate DataFrame display to use IPython's _repr_mimebundle_() protocol for anywidget mode #2271
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
refactor: Migrate DataFrame display to use IPython's _repr_mimebundle_() protocol for anywidget mode #2271
Changes from 15 commits
0dc287e
94c05d7
1b15ef0
53953bb
5397a59
689fa74
6b407d4
3cd960e
b0f1525
7e47895
736929a
eb61fc0
3079a3c
86e0b6a
d53eea3
8b9a2cf
1042a9e
83513b5
05a9245
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -789,9 +789,7 @@ def __repr__(self) -> str: | |||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| opts = bigframes.options.display | ||||||||||||||||||||||||||||||||||||||||||||||||
| max_results = opts.max_rows | ||||||||||||||||||||||||||||||||||||||||||||||||
| # anywdiget mode uses the same display logic as the "deferred" mode | ||||||||||||||||||||||||||||||||||||||||||||||||
| # for faster execution | ||||||||||||||||||||||||||||||||||||||||||||||||
| if opts.repr_mode in ("deferred", "anywidget"): | ||||||||||||||||||||||||||||||||||||||||||||||||
| if opts.repr_mode == "deferred": | ||||||||||||||||||||||||||||||||||||||||||||||||
| return formatter.repr_query_job(self._compute_dry_run()) | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| # TODO(swast): pass max_columns and get the true column count back. Maybe | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -829,7 +827,7 @@ def __repr__(self) -> str: | |||||||||||||||||||||||||||||||||||||||||||||||
| lines.append(f"[{row_count} rows x {column_count} columns]") | ||||||||||||||||||||||||||||||||||||||||||||||||
| return "\n".join(lines) | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| def _repr_html_(self) -> str: | ||||||||||||||||||||||||||||||||||||||||||||||||
| def _repr_html_fallback(self) -> str: | ||||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||||
| Returns an html string primarily for use by notebooks for displaying | ||||||||||||||||||||||||||||||||||||||||||||||||
| a representation of the DataFrame. Displays 20 rows by default since | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -840,57 +838,148 @@ def _repr_html_(self) -> str: | |||||||||||||||||||||||||||||||||||||||||||||||
| if opts.repr_mode == "deferred": | ||||||||||||||||||||||||||||||||||||||||||||||||
| return formatter.repr_query_job(self._compute_dry_run()) | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| # Process blob columns first, regardless of display mode | ||||||||||||||||||||||||||||||||||||||||||||||||
| # Process blob columns first for non-deferred modes | ||||||||||||||||||||||||||||||||||||||||||||||||
| df, blob_cols = self._process_blob_columns() | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| pandas_df, row_count, query_job = df._block.retrieve_repr_request_results( | ||||||||||||||||||||||||||||||||||||||||||||||||
| max_results | ||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| self._set_internal_query_job(query_job) | ||||||||||||||||||||||||||||||||||||||||||||||||
| column_count = len(pandas_df.columns) | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| return self._create_html_representation( | ||||||||||||||||||||||||||||||||||||||||||||||||
| pandas_df, row_count, column_count, blob_cols | ||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| def _process_blob_columns(self) -> tuple[DataFrame, list[str]]: | ||||||||||||||||||||||||||||||||||||||||||||||||
| """Process blob columns for display.""" | ||||||||||||||||||||||||||||||||||||||||||||||||
| self._cached() | ||||||||||||||||||||||||||||||||||||||||||||||||
| df = self.copy() | ||||||||||||||||||||||||||||||||||||||||||||||||
| df = self | ||||||||||||||||||||||||||||||||||||||||||||||||
| blob_cols = [] | ||||||||||||||||||||||||||||||||||||||||||||||||
| if bigframes.options.display.blob_display: | ||||||||||||||||||||||||||||||||||||||||||||||||
| blob_cols = [ | ||||||||||||||||||||||||||||||||||||||||||||||||
| series_name | ||||||||||||||||||||||||||||||||||||||||||||||||
| for series_name, series in df.items() | ||||||||||||||||||||||||||||||||||||||||||||||||
| for series_name, series in self.items() | ||||||||||||||||||||||||||||||||||||||||||||||||
| if series.dtype == bigframes.dtypes.OBJ_REF_DTYPE | ||||||||||||||||||||||||||||||||||||||||||||||||
| ] | ||||||||||||||||||||||||||||||||||||||||||||||||
| for col in blob_cols: | ||||||||||||||||||||||||||||||||||||||||||||||||
| # TODO(garrettwu): Not necessary to get access urls for all the rows. Update when having a to get URLs from local data. | ||||||||||||||||||||||||||||||||||||||||||||||||
| df[col] = df[col].blob._get_runtime(mode="R", with_metadata=True) | ||||||||||||||||||||||||||||||||||||||||||||||||
| if blob_cols: | ||||||||||||||||||||||||||||||||||||||||||||||||
| df = self.copy() | ||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+856
to
+865
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we define
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||
| for col in blob_cols: | ||||||||||||||||||||||||||||||||||||||||||||||||
| df[col] = df[col].blob._get_runtime(mode="R", with_metadata=True) | ||||||||||||||||||||||||||||||||||||||||||||||||
| return df, blob_cols | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| def _get_anywidget_bundle(self, include=None, exclude=None): | ||||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||||
| Helper method to create and return the anywidget mimebundle. | ||||||||||||||||||||||||||||||||||||||||||||||||
| This function encapsulates the logic for anywidget display. | ||||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||||
| from bigframes import display | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| df, _ = self._process_blob_columns() | ||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||
| df, _ = self._process_blob_columns() | |
| # TODO(shuowei): Keep blob_cols and pass them to TableWidget so that they can render properly. | |
| df, _ = self._process_blob_columns() |
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: pass is only necessary if there is no body, but we do have a body with the warning above.
Also, we are manually falling back.
| # Fallback: let IPython use _repr_html_fallback() instead | |
| warnings.warn( | |
| "Anywidget mode is not available. " | |
| "Please `pip install anywidget traitlets` or `pip install 'bigframes[anywidget]'` to use interactive tables. " | |
| f"Falling back to deferred mode. Error: {traceback.format_exc()}" | |
| f"Falling back to static HTML. Error: {traceback.format_exc()}" | |
| ) | |
| return formatter.repr_query_job(self._compute_dry_run()) | |
| # Don't return anything - let IPython fall back to _repr_html_fallback() | |
| pass | |
| # Anywidget is an optional dependency, so warn rather than fail. | |
| # TODO(shuowei): When Anywidget becomes the default for all repr modes, | |
| # remove this warning. | |
| warnings.warn( | |
| "Anywidget mode is not available. " | |
| "Please `pip install anywidget traitlets` or `pip install 'bigframes[anywidget]'` to use interactive tables. " | |
| f"Falling back to static HTML. Error: {traceback.format_exc()}" | |
| ) | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems like the wrong function in which to call
_cached()._cached()can be very expensive (causesa query). Seems better to just call it once if needed inrepr_mimebundle`.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The call to
_cached()was removed from_process_blob_columns(now_get_display_df_and_blob_cols) to avoid unnecessary queries, as suggested.