Skip to content

Deduplicate the optional FigureWidget import in graph_objects codegen - #5850

Open
DawnofGenX wants to merge 2 commits into
plotly:mainfrom
DawnofGenX:dedupe-figurewidget-codegen-5849
Open

DawnofGenX wants to merge 2 commits into
plotly:mainfrom
DawnofGenX:dedupe-figurewidget-codegen-5849

Conversation

@DawnofGenX

Copy link
Copy Markdown

Link to issue

Closes #5849

Description of change

The code generator wrote the optional FigureWidget import block (the try: import ... plus the missing_* fallback) into both plotly/graph_objs/__init__.py and plotly/graph_objects/__init__.py, leaving an identical block at the end of each generated file. graph_objects re-exports every other class from graph_objs — including Figure — and graph_objs already owns the optional import and the fallback, so the generator now emits the block only for graph_objs and adds ..graph_objs.FigureWidget to graph_objects' re-export list. The change is in codegen/__init__.py; plotly/graph_objects/__init__.py is regenerated (python commands.py codegen), not hand-edited.

Demo

N/A — no user-visible or visual change. Behaviour is unchanged: plotly.graph_objects.FigureWidget is the same object as plotly.graph_objs.FigureWidget, and still falls back to plotly.missing_anywidget.FigureWidget when ipywidgets is unavailable.

Testing strategy

Adds tests/test_core/test_figure_widget_backend/test_figurewidget_reexport.py, which asserts the three invariants: graph_objects.FigureWidget is graph_objs.FigureWidget, the forced-ipywidgets-failure fallback still resolves, and plotly/graph_objects/__init__.py contains no optional-ipywidgets import block of its own. The last assertion is the regression signal — it fails on unmodified main.

Additional information (optional)

Ran python commands.py codegen on the committed tree with the pinned ruff and it produces no diff, so the generated file and the generator agree.

Guidelines

@camdecoster camdecoster self-assigned this Oct 27, 2026

@camdecoster camdecoster left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the update. Could you please make these changes?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please remove this test? It seems like overkill.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed in 61bb49e. tests/test_core/test_figure_widget_backend/test_figurewidget_reexport.py is gone; the rest of the suite still passes (19 passed in tests/test_core/test_figure_widget_backend + tests/test_core/test_optional_imports).

Comment thread CHANGELOG.md

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please revert these changes? This is an internal update and doesn't need a changelog entry.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reverted in 61bb49e — CHANGELOG.md is back to upstream and no longer appears in the diff. One heads-up: the Check changelog workflow fails when a PR has no CHANGELOG change unless it carries the no-changelog label, and I can't add labels. Could you add it when you next look at this?

Comment thread codegen/__init__.py Outdated
Comment on lines +306 to +314
@@ -307,6 +307,11 @@ def __getattr__(import_name):
"..graph_objs." + rel_path.split(".")[-1]
for rel_path in datatype_rel_class_imports[()]
]
# FigureWidget is owned by graph_objs, which handles the optional
# ipywidgets import and the missing_* fallback. Re-export it from
# graph_objs like every other class instead of duplicating the optional
# import block in graph_objects.
graph_objects_rel_classes.append("..graph_objs.FigureWidget")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's simplify this a bit:

Suggested change
graph_objects_rel_classes.append("..graph_objs.FigureWidget")
graph_objects_rel_classes = [
*("..graph_objs." + rel_path.split(".")[-1] for rel_path in datatype_rel_class_imports[()]),
# graph_objs owns the optional ipywidgets import and the fallback
"..graph_objs.FigureWidget",
]

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applied in 61bb49e. ruff format --check (which CI runs over the whole repo) insists on wrapping the generator, so the committed form is:

    graph_objects_rel_classes = [
        *(
            "..graph_objs." + rel_path.split(".")[-1]
            for rel_path in datatype_rel_class_imports[()]
        ),
        # graph_objs owns the optional ipywidgets import and the fallback
        "..graph_objs.FigureWidget",
    ]

Same thing as your suggestion, just formatter-stable. ruff format --check codegen/__init__.py and ruff check codegen/__init__.py both pass, and running python commands.py codegen is a no-op on the tree.

The code generator emitted the optional FigureWidget import block
(try: import ipywidgets / missing_* fallback) into both
plotly/graph_objs/__init__.py and plotly/graph_objects/__init__.py,
producing an identical block at the end of each generated file.

graph_objects already re-exports every other class from graph_objs
(including Figure), and graph_objs owns the optional ipywidgets import
and the missing_* fallback. Emit the block only for graph_objs and
re-export FigureWidget from graph_objs in graph_objects like every
other class.

Behaviour is unchanged: plotly.graph_objects.FigureWidget still resolves
to the real class when ipywidgets is present and to the missing_*
fallback when it is absent.

Add a regression test proving the invariant (identity, forced-failure
fallback, and no duplicated ipywidgets block in graph_objects).

Fixes plotly#5849
@DawnofGenX
DawnofGenX force-pushed the dedupe-figurewidget-codegen-5849 branch from 6e5184b to ad6cb64 Compare November 11, 2026 04:03
@DawnofGenX
DawnofGenX force-pushed the dedupe-figurewidget-codegen-5849 branch from ad6cb64 to 61bb49e Compare November 11, 2026 04:08
@DawnofGenX

Copy link
Copy Markdown
Author

All three review points are addressed in 61bb49e (rebased onto current main):

  1. Deleted tests/test_core/test_figure_widget_backend/test_figurewidget_reexport.py.
  2. Reverted the CHANGELOG.md entry — the PR diff is now only codegen/__init__.py and plotly/graph_objects/__init__.py.
  3. Applied the suggested inline list form at codegen/__init__.py (the generator is wrapped because ruff format --check runs over the whole repo in CI).

Verification on the pushed head:

  • ruff format --check / ruff check clean on the changed files
  • python commands.py codegen is a no-op on the tree (no diff after regeneration)
  • pytest tests/test_core/test_figure_widget_backend tests/test_core/test_optional_imports → 19 passed
  • FigureWidget still resolves to plotly.graph_objs._figurewidget.FigureWidget, stays in graph_objs.__all__, and raises the anywidget ImportError when the optional dependency is missing

One thing I can't do myself: the Check changelog workflow will fail on this PR now that the changelog change is reverted, unless it gets the no-changelog label — only maintainers can add labels.

Could you take another look when you get a chance?

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG]: Duplicate FigureWidget import code in graph_objects and graph_objs

2 participants