From 659b7486c1e6b52279089dce4b7efd2efa0e2600 Mon Sep 17 00:00:00 2001 From: Luke Davis Date: Thu, 19 Aug 2021 13:22:22 -0600 Subject: [PATCH] Avoid class-level conflicts, begin to find instance-level conflicts --- proplot/artist.py | 96 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 66 insertions(+), 30 deletions(-) diff --git a/proplot/artist.py b/proplot/artist.py index a07fe24c8..86a39e680 100644 --- a/proplot/artist.py +++ b/proplot/artist.py @@ -1,50 +1,86 @@ #!/usr/bin/env python3 """ -Create a metaclass +Add dot-notation properties for matplotlib setter and getter functions. """ +import warnings + import matplotlib.artist as martist +from matplotlib import MatplotlibDeprecationWarning __all__ = [] -PROPS_PROTECTED = [ - 'figure', 'axes', 'pickradius', -] + +PROPS_IGNORE = ( + # Axes props + 'axes', + 'figure', + 'xaxis', # axes prop + 'yaxis', # axes prop + 'zaxis', # axes prop + 'units', # axis prop + 'gridlines', + # Method conflicts + 'legend', + 'tight_layout', + # Class-level conflicts + 'contains', + 'zorder', + 'pickradius', # line property related to 'contains' + # Instance-level artist conflicts + 'label', + 'label_position', + # Instance-level axes conflicts + 'cmap', + 'norm', + 'lines', + 'images', + 'title', # TODO: use internal title handling +) -def _gen_subclasses(cls): +def _iter_subclasses(cls): """ - Generate all of a class's sublcasses with recursion. + Iterate through all subclasses. """ + yield cls try: for subclass in cls.__subclasses__(): - yield subclass - for subclass in _gen_subclasses(subclass): - yield subclass + yield from _iter_subclasses(subclass) except TypeError: - return + pass -def _gen_properties(cls): +def _add_properties(cls): """ Generate property definitions for every artist getter. """ for attr in dir(cls): - obj = getattr(cls, attr) - if callable(obj) and attr[:4] == 'get_': - getter = obj - name = attr[4:] - if hasattr(cls, name): - continue - elif name in PROPS_PROTECTED: - continue - args = [getter] # property args - setter = getattr(cls, 'set_' + name, None) - if callable(setter): - args.append(setter) - yield name, property(*args, doc=getter.__doc__) - - -for cls in _gen_subclasses(martist.Artist): - for name, prop in _gen_properties(cls): - setattr(cls, name, prop) - print(f'Added properties to {cls.__name__}.') + try: + getter = getattr(cls, attr) + except MatplotlibDeprecationWarning: + continue + if not callable(getter) or attr[:4] != 'get_': + continue + prop = attr[4:] + if prop in PROPS_IGNORE: + continue + if hasattr(cls, prop): + value = getattr(cls, prop) + if not isinstance(value, property): # i.e. this is not child of a class + warnings._warn_proplot(f'Skipping property {prop!r}. Already exists as attribute.') # noqa: E501 + continue + args = [getter] # property() function args + setter = getattr(cls, 'set_' + prop, None) + if callable(setter): + args.append(setter) + obj = property(*args, doc=getter.__doc__) + setattr(cls, prop, obj) + + +# Apply properties +# NOTE: While we can guard against class-level attribute conflicts we *cannot* guard +# against instance-level attribute conflicts. Therefore this may never work. +for cls in _iter_subclasses(martist.Artist): + with warnings.catch_warnings(): + warnings.simplefilter('error', MatplotlibDeprecationWarning) + _add_properties(cls)