Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork7.9k
Bezier/Path API Cleanup: fix circular import issue#16812
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Uh oh!
There was an error while loading.Please reload this page.
Conversation
brunobeltran commentedMar 17, 2020 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
I have concerns about moving public functions from public module. While I'm not sure I disagree with the analysis of what the organizationshould be (also not sure if I agree), our user base is very large and very clever. The probability approaches one that we have users who are using these functions and we can not move them with no warning.
It looks like the minimal fix to prevent the circular imports is to move thefrom .path import Path
to be local tomake_path_regular
andconcatenate_paths
I am 👍 on the enthusiasm! However, I have concerns about the unintended consequences. |
brunobeltran commentedMar 18, 2020 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
Thanks for the positive feedback,@tacaswell !
Even easier, I made them just wrap the appropriate method calls (of the
Would you be more in favor of slowly deprecating these functions? I obviously don't mind them being perpetually duplicated in |
Actually, I am deprecating make_path_regular and concatenate_paths in#14199; can that get reviewed first? Both functions actually have relatively simple alternatives available. |
@tacaswell , what is the expectation before merging, should this branch be rebased to be a single commit? Or do you prefer to preserve the whole commit history? |
8f67987
to1a7b35b
CompareIf you make 50 commits where one would do (e.g., if there are multiple test image changes), we'd ask to squash, but otherwise we don't bother. |
fea08e8
tod533912
CompareThis PR is now ready for re-review pending merge of#14199. After that#14199 is merged, this PR will basically just:
|
9008c5c
todf203f8
CompareSquashed all commits into one, since a bunch of other PRs depend on this, to make it easier to review them. |
Uh oh!
There was an error while loading.Please reload this page.
lib/matplotlib/bezier.py Outdated
@cbook.deprecated("3.2", alternative="Path.make_compound_path()") | ||
def split_path_inout(path, inside, tolerance=0.01, reorder_inout=False): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
You can just reimport this function at the top of the module (from .path import split_path_inout
with a note that this needs to remain available in this module for API stability reasons) and then not bother with the deprecation (if the implementation stays around anywhere in the library, the disruption of removing it isn't really worth it).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
The idea behind this pull request is to removefrom .path import ...
from the top ofbezier.py
. This is because I implemented all the functions to do e.g. MarkerStyle renormalization (and other things), but they are all methods ofPath
, and they need access toBezierSegment
. Currently, I cannotfrom .bezier import ...
inpath.py
becausebezier.py
already importsfrom .path ...
. So reimporting this function at the top of the module would defeat the purpose.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Ah. OK, but then at theend of path.py you can dobezier.split_path_inout = Path.split_path_inout
(with a comment re: conserving old API)? This way people doingfrom bezier import split_path_inout
won't be affected?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Maybe I'm missing something, but as in other comment, it seems like this patch would only work if it was applied at the package level? For example inlib/matplotlib/__init__.py
I could add
import matplotlib.patchesimport matplotlib.pathimport matplotlib.bezierbezier.split_path_inout = Path.split_path_inoutbezier.inside_circle = patches._inside_circle
but I don't see how that is better than my current approach, of just keeping the functions inbezier.py
and calling their new implementations. My approach also doesn't have any repeated code, and doesn't affect anybody doingfrom bezier import split_path_inout
.
If your argument is that we shouldn't deprecate these functions at all, I am happy to remove the@deprecated
statements, since thereal point of this PR was to remove thefrom .path import Path
statement frombezier.py
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
it is better to keep these sort of details contained inside of the modules that affect them. Havingpath.py
monkey-patchbezier
is less bad than doing the monkey patching in the top level__init__.py
It looks like you are moving all of the helper methods frombezier.py
->path.py
then it is OK to keep a.path
import inbezier.py
?
brunobeltranMar 22, 2020 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
@tacaswell, I agree. That's why I didn't do it in the first place.
Maybe I'm missing something, but the proposed monkey patch doesn't solve the stated problem (of not changing the public API)?
I think I have done a poor job explaining the purpose of this PR: the point is to remove the.path
import frombezier.py
. Why? Because many things that you might want to compute about Paths (bounding box, area, center of mass, see e.g.#16832,#16859) need to call functions ofbezier.py
(and these are crucial for improvements that you guys seem to largely be behind, such as the equal area marker size fix). If we leave the.path
import inbezier.py
, it is not possible to implement those new features in a reasonable way.
Conversely, the current reason for the existence of the import is that a bunch of functions that have no business being inbezier.py
in the first place.
So one solution is to move the.path
import into each of those functions, and leave everything else as is. I wouldn't prefer this, but I'm also not responsible for maintaining this code, so I am happy to do this instead.
Another, better solution (this PR), is to move the functions where they ought to have been in the first place and havebezier.whatever
just pass thru to the new version of the function. (And optionally deprecate the old versions).
My understanding is that instead of this pass-thru approach,@anntzer would prefer that I monkey patch the functions back in. But this breaks the public API.
With my current approach:
from matplotlib import bezierbezier.split_path_inout
Works.
With@anntzer's monkey patch approach as I understand it, you need to do
from matplotlib import bezierbezier.split_path_inout # errorfrom matplotlib import pathbezier.split_path_inout # works
Sorry if reply sounds terse, I'm on mobile.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
But if you have moved all of the useful functions topath.py
it no longerhas to importbezier
so it is no longer circular. Are there more functions than these that you want to move?
We could also pull the common code out to a new module that bothpath.py
andbezier.py
import from.
As a general rule, we try to avoid both API changes and code-churn for the sake of aesthetics changes. The question I have is: Is this is theminimal API and code change that we can do to get to the desired outcome?
brunobeltranMar 23, 2020 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Ah, I see where you're coming from now, it was just me not doing a good job explaining why this PR exists in the first place. I'm 100% on-board with doing my best to minimize API changes and code-churn. (For example, I am happy to leaveinside_circle
where it is, I am really only after the moving ofsplit_path_inout
out ofbezier.py
so that it no longer has to importfrom .path
).
However, this is already the minimum API change required so that I can do#16832,#16859 (and more), which are not-just-aesthetic. Basically the confusion seems to be that since those new functions aren't in this PR, the PR doesn't seem justified.
In short, I moved all the (currently) useful functions topath.py
. There not more functions that I want to move, but there are a half-dozen functions that I have written (with the end-goal of equal-area markers) that depend on.path
being able tofrom bezier import BezierSegment
(again, that class is empty in this PR, but that's only because I wanted to split the PRs into manageable chunks to make review easier).
brunobeltranMar 23, 2020 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Maybe it makes sense for me to make a "BIG" PR of my "equal area markers" branch (which I currently have split into#16812,#16832,#16859, (and one more that I haven't pushed up yet)).
That way we can discuss the top-level planning of how to go about the equal area markers thing? Given my current strategy (writing code to compute the area of an arbitrary path, which turns out to be quite simple, see#16859), this PR is a necessary first step.
lib/matplotlib/bezier.py Outdated
@cbook.deprecated("3.2") | ||
def inside_circle(cx, cy, r): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
As for split_path_inout I would just leave this as public API reexported into this module, for now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Public API where? Currently I've moved it intopatches.py
, which is the only place where it is used. I hesitate to makeinside_circle
a part of the public API of that module, since it's such a non-public-facing part of the module's internals.
In addition, we can't REALLY just re-exporting it into this module from there, sincepatches.py
already doesfrom .bezier import ...
. I fixed this now so it doesn't explicitly repeat the code, which I figure is better so that the implementations don't diverge.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
As above, you can patch bezier.py to add this function to it at the end of patches.py? In fact it can stay private in patches, and be "public" in bezier (bezier.inside_circle = _inside_circle
at the end of patches.py).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
but then the API will only remain unchanged as long as the user imports patches. If they import bezier only, the patch will not be applied and the functions will be missing?
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
e805071
to4943d33
CompareI agree we need to step back and get a broader view of what we are doing here.
I do not agree it is obvious,
Again, I'm not sure that this is obvious. I am assuming that these helpers are things that only need to use the public API of I think we could we avoid the need for this re-arrangement if we make a In addition to avoiding this code churn, starting with the helpers in a private module lets us be less careful about merging things and in changing what is there independent of how the totality of this work gets relative to version releases etc. Again I am 👍 on this work in principle and looking for the path of least resistance to getting this code in. |
Thanks as usual for the positive feedback! I have opened a PR (#16891) with one proposed architecture for the new changes. I think a lot of this discussion would best take place there, and this PR can be revisited once we're all on the same page as to the best top-level architectural decisions for how to add this new code. I'll answer your comments here for the record, but will reproduce these points in#16891, so we can continue the discussion there.
You're right, obvious was the wrong word. There are two equally-valid interpretations of the relationship of What this PR is implicitly proposing is that the most natural way to architect this library going forward is to commit to (1). This is because anything you want to compute about a path (arc length, area, bounding box, etc) you do it by computing it for each BezierSegment making up the path. And if you go with (2) instead, then you have to end up polluting
I definitely agree that all of this extra code could for now be put into a private module, like If we decide thatnone of these functions eventually need to be public, then I will (reluctantly lol) close this PR and move all of my new code into a private helper module. |
Given that this enables#16832 which has a concrete use to fix a bug, and there wasmostly agreement on the last call for this one, I'm not sure we need to hold this up for the more abstract |
Based on feedback from the call today, this PR has been changed to not actually perform any API changes. A new PR will be created with proposed API changes independently of this PR stack, which will now only contain feature improvements. |
... since the deprecations went in, this is a trivial re-arrangements of the imports in the two deprecated methods. |
just waiting for ci |
The commit message was a bit weird, since it's not a backport of anything. I used a squash merge so I could change the message without having to wait for all the CI to re-run needlessly. Unfortunately, this does mean the other PRs need rebasing. |
@QuLogic, thanks for fixing that, the commit message only made sense in the context of my personal branches. |
Uh oh!
There was an error while loading.Please reload this page.
PR Summary
Roadmap:
(This PR) (*) <-#16832 (*) <-#16859 (*) <-#16888 <-#16889 (*) <-#16891 (MarkerStyle improvements!)
"<-" means "depends on", and "(*)" marks PRs whose implementations are complete and fully ready for review.
Currently,
bezier.py
depends onpath.py
. While it's easy to see how this could happen, this dependency should pretty obviously be reversed.bezier.py
contains helper functions that are designed to help compute things about bezier curves.path.py
defines aPath
, and all computations that are "about paths" should be inclass Path
.This became a show-stopper in#16607,#16832 and#16859) when I wanted to write methods that could compute things about paths (like their extrema, area, center of mass...all required for the marker size fixes proposed in#15703). I want to be able to define a member of
Path
that uses helper functions inbezier.py
to help it compute bounds, etc., but currently cannot because that would cause a circular import (path.py
- imports ->bezier.py' - imports ->
path.py').This is fixed here by moving a couple of functions from
bezier.py
that really compute things aboutpaths (not about bezier curves necessarily) intopath.py
.EDIT: original list of functions to deprecate is now shorter, since@Annzter deprecated
make_path_regular
andconcatenate_paths
in his own PR after my initial submission.bezier.split_path_inout
is nowPath.split_path_inout
, since its first argument is more naturally "self". This allows removing the import.patches.py
(not really anything to do with bezier curves) moved there.The changes to the API I made seem likely to be internal-facing only, since
bezier.py
was obviously intended to be used internally bymatplotlib
to help with implementingpatches.py
,path.py
, etc.Per@tacaswell's suggestion, the original functions were kept around (as pass-thru/wrappers) and deprecated instead of removed.
No tests were added since no new code was added, only moved around.
PR Checklist