diff --git a/lib/mpl_toolkits/mplot3d/axes3d.py b/lib/mpl_toolkits/mplot3d/axes3d.py index 3aaf31610d29..e883da3f8b96 100644 --- a/lib/mpl_toolkits/mplot3d/axes3d.py +++ b/lib/mpl_toolkits/mplot3d/axes3d.py @@ -12,6 +12,7 @@ from collections import defaultdict import functools +import inspect import itertools import math from numbers import Integral @@ -412,24 +413,27 @@ def do_3d_projection(artist): Call `do_3d_projection` on an *artist*, and warn if passing *renderer*. - For our Artists, never pass *renderer*. For external Artists, - in lieu of more complicated signature parsing, always pass - *renderer* and raise a warning. + Attempt to bind the empty signature first, so external Artists + can avoid the deprecation warning if they support the new + calling convention. """ - - if artist.__module__ == 'mpl_toolkits.mplot3d.art3d': - # Our 3D Artists have deprecated the renderer parameter, so - # avoid passing it to them; call this directly once the - # deprecation has expired. + try: + signature = inspect.signature(artist.do_3d_projection) + signature.bind() + # ValueError if `inspect.signature` cannot provide a signature + # and TypeError if the binding fails or the object does not + # appear to be callable - the next call will then re-raise. + except (ValueError, TypeError): + _api.warn_deprecated( + "3.4", + message="The 'renderer' parameter of " + "do_3d_projection() was deprecated in Matplotlib " + "%(since)s and will be removed %(removal)s.") + return artist.do_3d_projection(renderer) + else: + # Call this directly once the deprecation period expires. return artist.do_3d_projection() - _api.warn_deprecated( - "3.4", - message="The 'renderer' parameter of " - "do_3d_projection() was deprecated in Matplotlib " - "%(since)s and will be removed %(removal)s.") - return artist.do_3d_projection(renderer) - collections_and_patches = ( artist for artist in self._children if isinstance(artist, (mcoll.Collection, mpatches.Patch)) diff --git a/lib/mpl_toolkits/tests/test_mplot3d.py b/lib/mpl_toolkits/tests/test_mplot3d.py index c3d107b40494..a4cb6dfde0aa 100644 --- a/lib/mpl_toolkits/tests/test_mplot3d.py +++ b/lib/mpl_toolkits/tests/test_mplot3d.py @@ -1711,3 +1711,63 @@ def test_view_init_vertical_axis( tickdir_expected = tickdirs_expected[i] tickdir_actual = axis._get_tickdir() np.testing.assert_array_equal(tickdir_expected, tickdir_actual) + + +def test_do_3d_projection_renderer_deprecation_warn_on_argument(): + """ + Test that an external artist with an old-style calling convention raises + a suitable deprecation warning. + """ + class DummyPatch(art3d.Patch3D): + def do_3d_projection(self, renderer): + return 0 + + def draw(self, renderer): + pass + + fig = plt.figure() + ax = fig.add_subplot(111, projection='3d') + artist = DummyPatch() + ax.add_artist(artist) + + match = r"The 'renderer' parameter of do_3d_projection\(\) was deprecated" + with pytest.warns(MatplotlibDeprecationWarning, match=match): + fig.canvas.draw() + + +def test_do_3d_projection_renderer_deprecation_nowarn_on_optional_argument(): + """ + Test that an external artist with a calling convention compatible with + both v3.3 and v3.4 does not raise a deprecation warning. + """ + class DummyPatch(art3d.Patch3D): + def do_3d_projection(self, renderer=None): + return 0 + + def draw(self, renderer): + pass + + fig = plt.figure() + ax = fig.add_subplot(111, projection='3d') + artist = DummyPatch() + ax.add_artist(artist) + fig.canvas.draw() + + +def test_do_3d_projection_renderer_deprecation_nowarn_on_no_argument(): + """ + Test that an external artist with a calling convention compatible with + only v3.4 does not raise a deprecation warning. + """ + class DummyPatch(art3d.Patch3D): + def do_3d_projection(self): + return 0 + + def draw(self, renderer): + pass + + fig = plt.figure() + ax = fig.add_subplot(111, projection='3d') + artist = DummyPatch() + ax.add_artist(artist) + fig.canvas.draw()