Design follow-up from #698 / #677. Not a bug — the refactor is behaviour-preserving and the full suite is green. This is about whether the seam is where the plan says it is.
The manager sequencing in #677 positions SubplotManager as the leaf node: "SubplotManager: (standalone, only needs gridspec)". As extracted, it isn't standalone.
The dependency is bidirectional
SubplotManager reaches back into Figure for:
fig._layout_dirty
fig._format_signature
fig.add_subplot(...)
fig.format(...)
- a function-local
from .figure import Figure (_subplots.py:256) needed to call super(Figure, fig).add_subplot(...) — a deferred import that exists purely to break an import cycle.
And Figure's collaborators now call back through the figure to reach the manager: gridspec.py and axes/base.py use fig._get_subplot / fig._iter_subplots.
There's also a round trip: SubplotManager.add_subplots calls fig.add_subplot(...) (the public, decorated method) rather than self.add_subplot(...), so every subplot goes manager → figure → manager just to pick up @_clear_border_cache.
Net: the code moved to a new file, but the coupling didn't shrink. That's fine as a first step — it just means the "standalone" edge in the #677 dependency graph isn't real yet, and the managers that are supposed to depend on this one will inherit the cycle.
Bonus: parse_proj doesn't belong here
SubplotManager.parse_proj never uses self except to call self.parse_backend(...), which is itself a @staticmethod. Projection parsing is a pure function of its arguments, and the class docstring already admits it's a third concern ("subplot creation, gridspec ownership, and projection parsing").
Making it a @staticmethod or a module-level function would be a strict simplification — and it also makes the fix for the ui.py signature coupling (filed separately) trivial.
Design follow-up from #698 / #677. Not a bug — the refactor is behaviour-preserving and the full suite is green. This is about whether the seam is where the plan says it is.
The manager sequencing in #677 positions
SubplotManageras the leaf node: "SubplotManager: (standalone, only needs gridspec)". As extracted, it isn't standalone.The dependency is bidirectional
SubplotManagerreaches back intoFigurefor:fig._layout_dirtyfig._format_signaturefig.add_subplot(...)fig.format(...)from .figure import Figure(_subplots.py:256) needed to callsuper(Figure, fig).add_subplot(...)— a deferred import that exists purely to break an import cycle.And
Figure's collaborators now call back through the figure to reach the manager:gridspec.pyandaxes/base.pyusefig._get_subplot/fig._iter_subplots.There's also a round trip:
SubplotManager.add_subplotscallsfig.add_subplot(...)(the public, decorated method) rather thanself.add_subplot(...), so every subplot goes manager → figure → manager just to pick up@_clear_border_cache.Net: the code moved to a new file, but the coupling didn't shrink. That's fine as a first step — it just means the "standalone" edge in the #677 dependency graph isn't real yet, and the managers that are supposed to depend on this one will inherit the cycle.
Bonus:
parse_projdoesn't belong hereSubplotManager.parse_projnever usesselfexcept to callself.parse_backend(...), which is itself a@staticmethod. Projection parsing is a pure function of its arguments, and the class docstring already admits it's a third concern ("subplot creation, gridspec ownership, and projection parsing").Making it a
@staticmethodor a module-level function would be a strict simplification — and it also makes the fix for theui.pysignature coupling (filed separately) trivial.