|
1 | 1 | #!/usr/bin/env python3
|
2 | 2 | """
|
3 |
| -Create a metaclass |
| 3 | +Add dot-notation properties for matplotlib setter and getter functions. |
4 | 4 | """
|
| 5 | +import warnings |
| 6 | + |
5 | 7 | import matplotlib.artist as martist
|
| 8 | +from matplotlib import MatplotlibDeprecationWarning |
6 | 9 |
|
7 | 10 | __all__ = []
|
8 | 11 |
|
9 |
| -PROPS_PROTECTED = [ |
10 |
| - 'figure', 'axes', 'pickradius', |
11 |
| -] |
| 12 | + |
| 13 | +PROPS_IGNORE = ( |
| 14 | + # Axes props |
| 15 | + 'axes', |
| 16 | + 'figure', |
| 17 | + 'xaxis', # axes prop |
| 18 | + 'yaxis', # axes prop |
| 19 | + 'zaxis', # axes prop |
| 20 | + 'units', # axis prop |
| 21 | + 'gridlines', |
| 22 | + # Method conflicts |
| 23 | + 'legend', |
| 24 | + 'tight_layout', |
| 25 | + # Class-level conflicts |
| 26 | + 'contains', |
| 27 | + 'zorder', |
| 28 | + 'pickradius', # line property related to 'contains' |
| 29 | + # Instance-level artist conflicts |
| 30 | + 'label', |
| 31 | + 'label_position', |
| 32 | + # Instance-level axes conflicts |
| 33 | + 'cmap', |
| 34 | + 'norm', |
| 35 | + 'lines', |
| 36 | + 'images', |
| 37 | + 'title', # TODO: use internal title handling |
| 38 | +) |
12 | 39 |
|
13 | 40 |
|
14 |
| -def _gen_subclasses(cls): |
| 41 | +def _iter_subclasses(cls): |
15 | 42 | """
|
16 |
| - Generate all of a class's sublcasses with recursion. |
| 43 | + Iterate through all subclasses. |
17 | 44 | """
|
| 45 | + yield cls |
18 | 46 | try:
|
19 | 47 | for subclass in cls.__subclasses__():
|
20 |
| - yield subclass |
21 |
| - for subclass in _gen_subclasses(subclass): |
22 |
| - yield subclass |
| 48 | + yield from _iter_subclasses(subclass) |
23 | 49 | except TypeError:
|
24 |
| - return |
| 50 | + pass |
25 | 51 |
|
26 | 52 |
|
27 |
| -def _gen_properties(cls): |
| 53 | +def _add_properties(cls): |
28 | 54 | """
|
29 | 55 | Generate property definitions for every artist getter.
|
30 | 56 | """
|
31 | 57 | for attr in dir(cls):
|
32 |
| - obj = getattr(cls, attr) |
33 |
| - if callable(obj) and attr[:4] == 'get_': |
34 |
| - getter = obj |
35 |
| - name = attr[4:] |
36 |
| - if hasattr(cls, name): |
37 |
| - continue |
38 |
| - elif name in PROPS_PROTECTED: |
39 |
| - continue |
40 |
| - args = [getter] # property args |
41 |
| - setter = getattr(cls, 'set_' + name, None) |
42 |
| - if callable(setter): |
43 |
| - args.append(setter) |
44 |
| - yield name, property(*args, doc=getter.__doc__) |
45 |
| - |
46 |
| - |
47 |
| -for cls in _gen_subclasses(martist.Artist): |
48 |
| - for name, prop in _gen_properties(cls): |
49 |
| - setattr(cls, name, prop) |
50 |
| - print(f'Added properties to {cls.__name__}.') |
| 58 | + try: |
| 59 | + getter = getattr(cls, attr) |
| 60 | + except MatplotlibDeprecationWarning: |
| 61 | + continue |
| 62 | + if not callable(getter) or attr[:4] != 'get_': |
| 63 | + continue |
| 64 | + prop = attr[4:] |
| 65 | + if prop in PROPS_IGNORE: |
| 66 | + continue |
| 67 | + if hasattr(cls, prop): |
| 68 | + value = getattr(cls, prop) |
| 69 | + if not isinstance(value, property): # i.e. this is not child of a class |
| 70 | + warnings._warn_proplot(f'Skipping property {prop!r}. Already exists as attribute.') # noqa: E501 |
| 71 | + continue |
| 72 | + args = [getter] # property() function args |
| 73 | + setter = getattr(cls, 'set_' + prop, None) |
| 74 | + if callable(setter): |
| 75 | + args.append(setter) |
| 76 | + obj = property(*args, doc=getter.__doc__) |
| 77 | + setattr(cls, prop, obj) |
| 78 | + |
| 79 | + |
| 80 | +# Apply properties |
| 81 | +# NOTE: While we can guard against class-level attribute conflicts we *cannot* guard |
| 82 | +# against instance-level attribute conflicts. Therefore this may never work. |
| 83 | +for cls in _iter_subclasses(martist.Artist): |
| 84 | + with warnings.catch_warnings(): |
| 85 | + warnings.simplefilter('error', MatplotlibDeprecationWarning) |
| 86 | + _add_properties(cls) |
0 commit comments