Skip to content

Commit

Permalink
Avoid class-level conflicts, begin to find instance-level conflicts
Browse files Browse the repository at this point in the history
  • Loading branch information
lukelbd committed Aug 19, 2021
1 parent 6200379 commit 659b748
Showing 1 changed file with 66 additions and 30 deletions.
96 changes: 66 additions & 30 deletions proplot/artist.py
Original file line number Diff line number Diff line change
@@ -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)

0 comments on commit 659b748

Please sign in to comment.