-
Notifications
You must be signed in to change notification settings - Fork 104
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Avoid class-level conflicts, begin to find instance-level conflicts
- Loading branch information
Showing
1 changed file
with
66 additions
and
30 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |