Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Latest

## What's Changed
* :bug: keep `isinstance(fig, go.Figure)` and `isinstance(fig, go.FigureWidget)` working after `register_plotly_resampler()` (#364) by @raashish1601


# v0.11.1 (2026-10-04)
The README now points to [FlexViz](https://github.com/flex-analytics/flexviz), our successor project for whole-dashboard cross-filtering at scale.
Expand Down
38 changes: 30 additions & 8 deletions plotly_resampler/registering.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

__author__ = "Jeroen Van Der Donckt, Jonas Van Der Donckt, Emiel Deprost"

from functools import wraps

import plotly

from plotly_resampler import FigureResampler, FigureWidgetResampler
Expand Down Expand Up @@ -59,6 +57,24 @@ def _is_ipython_env():
return False


class _WrappedConstrMeta(type):
"""Metaclass of the wrapped constructors.

Delegates ``isinstance`` and ``issubclass`` checks to the original plotly class, so
that e.g. ``isinstance(fig, go.Figure)`` keeps working once registered.
"""

def __instancecheck__(cls, instance):
return isinstance(instance, cls.__wrapped__)

def __subclasscheck__(cls, subclass):
return issubclass(subclass, cls.__wrapped__)

def __getattr__(cls, name):
# Look up class attributes (e.g., static methods) on the original class
return getattr(cls.__wrapped__, name)


def _register_wrapper(
module: type,
constr_name: str,
Expand All @@ -68,14 +84,20 @@ def _register_wrapper(
constr = getattr(module, constr_name)
constr = _get_plotly_constr(constr) # get the original plotly constructor

# print(f"Wrapping {constr_name} with {pr_class}")

@wraps(constr)
def wrapped_constr(*args, **kwargs):
# print(f"Executing constructor wrapper for {constr_name}", constr)
def __new__(cls, *args, **kwargs):
# The returned object is not an instance of `cls`, so `__init__` is not called
return pr_class(constr(*args, **kwargs), **aggregator_kwargs)

wrapped_constr.__name__ = WRAPPED_PREFIX + constr_name
wrapped_constr = _WrappedConstrMeta(
WRAPPED_PREFIX + constr_name,
(),
{
"__new__": __new__,
"__wrapped__": constr,
"__doc__": constr.__doc__,
"__module__": constr.__module__,
},
)
setattr(module, constr_name, wrapped_constr)


Expand Down
40 changes: 29 additions & 11 deletions tests/test_registering.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
from inspect import isfunction

import numpy as np
import plotly.express as px
import plotly.graph_objects as go
Expand All @@ -9,6 +7,7 @@
AbstractFigureAggregator,
)
from plotly_resampler.registering import (
_already_wrapped,
_get_plotly_constr,
register_plotly_resampler,
unregister_plotly_resampler,
Expand All @@ -23,14 +22,14 @@ def test_get_plotly_const(registering_cleanup):
assert issubclass(FigureWidgetResampler, AbstractFigureAggregator)

# Is unregistered now
assert not (isfunction(go.Figure) or isfunction(go.FigureWidget))
assert not (_already_wrapped(go.Figure) or _already_wrapped(go.FigureWidget))
assert not issubclass(go.Figure, AbstractFigureAggregator)
assert not issubclass(go.FigureWidget, AbstractFigureAggregator)
assert not issubclass(_get_plotly_constr(go.Figure), AbstractFigureAggregator)
assert not issubclass(_get_plotly_constr(go.FigureWidget), AbstractFigureAggregator)

register_plotly_resampler()
assert isfunction(go.Figure) and isfunction(go.FigureWidget)
assert _already_wrapped(go.Figure) and _already_wrapped(go.FigureWidget)
assert isinstance(go.Figure(), AbstractFigureAggregator)
assert isinstance(go.FigureWidget(), AbstractFigureAggregator)
assert issubclass(FigureResampler, AbstractFigureAggregator)
Expand All @@ -39,25 +38,44 @@ def test_get_plotly_const(registering_cleanup):
assert not issubclass(_get_plotly_constr(go.FigureWidget), AbstractFigureAggregator)

unregister_plotly_resampler()
assert not (isfunction(go.Figure) or isfunction(go.FigureWidget))
assert not (_already_wrapped(go.Figure) or _already_wrapped(go.FigureWidget))
assert not issubclass(go.Figure, AbstractFigureAggregator)
assert not issubclass(go.FigureWidget, AbstractFigureAggregator)
assert not issubclass(_get_plotly_constr(go.Figure), AbstractFigureAggregator)
assert not issubclass(_get_plotly_constr(go.FigureWidget), AbstractFigureAggregator)


def test_isinstance_when_registered(registering_cleanup):
register_plotly_resampler(mode="figure")
fig = go.Figure()
assert isinstance(fig, FigureResampler)
assert isinstance(fig, go.Figure)
assert not isinstance(fig, go.FigureWidget)
assert issubclass(FigureResampler, go.Figure)
assert not issubclass(FigureResampler, go.FigureWidget)

register_plotly_resampler(mode="widget")
fig = go.FigureWidget()
assert isinstance(fig, FigureWidgetResampler)
assert isinstance(fig, go.FigureWidget)
assert isinstance(fig, go.Figure) is isinstance(fig, _get_plotly_constr(go.Figure))

unregister_plotly_resampler()
assert isinstance(fig, go.FigureWidget)


def test_register_and_unregister_graph_objects(registering_cleanup):
import plotly.graph_objects as go_

# Is unregistered now
assert not (isfunction(go_.Figure) or isfunction(go_.FigureWidget))
assert not (_already_wrapped(go_.Figure) or _already_wrapped(go_.FigureWidget))
fig = go_.Figure()
assert not isinstance(fig, AbstractFigureAggregator)
fig = go_.FigureWidget()
assert not isinstance(fig, AbstractFigureAggregator)

register_plotly_resampler()
assert isfunction(go_.Figure) and isfunction(go_.FigureWidget)
assert _already_wrapped(go_.Figure) and _already_wrapped(go_.FigureWidget)
fig = go_.Figure()
assert isinstance(fig, AbstractFigureAggregator)
assert isinstance(fig, FigureResampler)
Expand All @@ -68,7 +86,7 @@ def test_register_and_unregister_graph_objects(registering_cleanup):
assert not isinstance(fig, FigureResampler)

unregister_plotly_resampler()
assert not (isfunction(go_.Figure) or isfunction(go_.FigureWidget))
assert not (_already_wrapped(go_.Figure) or _already_wrapped(go_.FigureWidget))
fig = go_.Figure()
assert not isinstance(fig, AbstractFigureAggregator)
fig = go_.FigureWidget()
Expand All @@ -79,14 +97,14 @@ def test_register_and_unregister_graph_objs(registering_cleanup):
import plotly.graph_objs as go_

# Is unregistered now
assert not (isfunction(go_.Figure) or isfunction(go_.FigureWidget))
assert not (_already_wrapped(go_.Figure) or _already_wrapped(go_.FigureWidget))
fig = go_.Figure()
assert not isinstance(fig, AbstractFigureAggregator)
fig = go_.FigureWidget()
assert not isinstance(fig, AbstractFigureAggregator)

register_plotly_resampler()
assert isfunction(go_.Figure) and isfunction(go_.FigureWidget)
assert _already_wrapped(go_.Figure) and _already_wrapped(go_.FigureWidget)
fig = go_.Figure()
assert isinstance(fig, AbstractFigureAggregator)
assert isinstance(fig, FigureResampler)
Expand All @@ -97,7 +115,7 @@ def test_register_and_unregister_graph_objs(registering_cleanup):
assert not isinstance(fig, FigureResampler)

unregister_plotly_resampler()
assert not (isfunction(go_.Figure) or isfunction(go_.FigureWidget))
assert not (_already_wrapped(go_.Figure) or _already_wrapped(go_.FigureWidget))
fig = go_.Figure()
assert not isinstance(fig, AbstractFigureAggregator)
fig = go_.FigureWidget()
Expand Down