-
Notifications
You must be signed in to change notification settings - Fork 27
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 our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
pip metadata refactoring #680
base: main
Are you sure you want to change the base?
pip metadata refactoring #680
Conversation
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.
LGTM with some minor nitpicks.
414e227
to
59e4f7e
Compare
59e4f7e
to
c66f55b
Compare
There's too much going on in this single commit, so it's difficult to follow all the changes in the diff, please introduce them gradually. |
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.
Very much in favour of this work, needs some polishing though.
), | ||
docs=PIP_METADATA_DOC, | ||
) | ||
return None, None |
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.
This refactor isn't a 1:1 replacement - previously if a name was found it was carried over to other source checks. Here, if you extract name but can't find version your code will resort to returning (None,None)
which means we'll infer the name of the project from the URL instead, that doesn't sound right.
cachi2/core/package_managers/pip.py
Outdated
First, try to parse the setup.py script (if present) and extract name and version | ||
from keyword arguments to the setuptools.setup() call. If either name or version | ||
could not be resolved and there is a setup.cfg file, try to fill in the missing | ||
values from metadata.name and metadata.version in the .cfg file. | ||
repo_name = Path(repo_id.parsed_origin_url.path.removesuffix(".git")).name | ||
subpath = package_dir.subpath_from_root | ||
resolved_name = Path(repo_name).joinpath(subpath) | ||
return canonicalize_name(str(resolved_name).replace("/", "-")).strip("-.") |
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.
This extraction should be a separate commit. Actually 2:
- one to perform the extraction
- one to rearrange the logic
nitpick: While at it, you can get rid of removesuffix
and use stem
from Path instead of name
.
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 would like to not rearrange the commits in this PR at all + don't waste too much time here. Each commit would require modification in unit tests, and that is not trivial based on the extremely complicated logic in there. I chose a much easier path, so, delete everything and write this from scratch.
I know, in GitHub the PR is basically unreviewable. Sorry for that.
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 know, in GitHub the PR is basically unreviewable. Sorry for that.
First things first, GH has nothing to do with this, I review stuff locally with 50 lines of context usually and even with flags to git to detect moved code doesn't change the situation in any way.
I would like to not rearrange the commits in this PR at all + don't waste too much time here. Each commit would require modification in unit tests, and that is not trivial based on the extremely complicated logic in there. I chose a much easier path, so, delete everything and write this from scratch.
The only blocker for splitting here is the unit tests here, I agree. But given that this refactor actually changes the behavioral logic such that we don't return the same results as previously from the pip_metadata
helper function. Additionally, the tests that you introduce actually don't test the compound cases which is the whole point of the pip metadata querying logic (e.g. name comes from one project file, version from another one). Since it looks like you'll have to rework the unit tests quite a bit that gives you the opportunity to split the changes into commits - I have actually tried myself and was pretty straightforward except for the tests, but like I said, looks like you'll have to rework that.
c66f55b
to
af59166
Compare
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.
After having carefully gone through the unit tests which I didn't do in my first round of reviews I think we're actually opening us up for potential issues with pyproject.toml setup.py etc. mixed metadata.
I think that while we may cosmetically change the code and break the logic into smaller helper functions, we'll have to test the metadata querying in the compound way we're doing now.
cachi2/core/package_managers/pip.py
Outdated
# setup.py | ||
if setup_py.exists(): | ||
log.debug("Checking setup.py for metadata") | ||
name = setup_py.get_name() | ||
version = setup_py.get_version() | ||
|
||
if name and version: | ||
return name, version | ||
|
||
# setup.cfg | ||
if setup_cfg.exists(): | ||
log.debug("Checking setup.cfg for metadata") | ||
name = setup_cfg.get_name() | ||
version = setup_cfg.get_version() | ||
|
||
return name, version |
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.
This is still not a 1:1 refactor and I'd argue this is a breaking change in behaviour (although in rare cases, but it is) - if you can't infer version from e.g. pyproject.toml you try setup.py, but if setup.py only defines version, but not name, you'll overwrite the name to None and eventually fall back to infering the name from the repo URL identifier which is not what the previous behaviour did and I'm not sure we want this to be changed.
cachi2/core/package_managers/pip.py
Outdated
First, try to parse the setup.py script (if present) and extract name and version | ||
from keyword arguments to the setuptools.setup() call. If either name or version | ||
could not be resolved and there is a setup.cfg file, try to fill in the missing | ||
values from metadata.name and metadata.version in the .cfg file. | ||
repo_name = Path(repo_id.parsed_origin_url.path.removesuffix(".git")).name | ||
subpath = package_dir.subpath_from_root | ||
resolved_name = Path(repo_name).joinpath(subpath) | ||
return canonicalize_name(str(resolved_name).replace("/", "-")).strip("-.") |
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 know, in GitHub the PR is basically unreviewable. Sorry for that.
First things first, GH has nothing to do with this, I review stuff locally with 50 lines of context usually and even with flags to git to detect moved code doesn't change the situation in any way.
I would like to not rearrange the commits in this PR at all + don't waste too much time here. Each commit would require modification in unit tests, and that is not trivial based on the extremely complicated logic in there. I chose a much easier path, so, delete everything and write this from scratch.
The only blocker for splitting here is the unit tests here, I agree. But given that this refactor actually changes the behavioral logic such that we don't return the same results as previously from the pip_metadata
helper function. Additionally, the tests that you introduce actually don't test the compound cases which is the whole point of the pip metadata querying logic (e.g. name comes from one project file, version from another one). Since it looks like you'll have to rework the unit tests quite a bit that gives you the opportunity to split the changes into commits - I have actually tried myself and was pretty straightforward except for the tests, but like I said, looks like you'll have to rework that.
That's a good point. The fact that we were mixing metadata from multiple project configuration files was there reason why we ended up with extremely complicated and long unit tests. Splitting name and version into multiple configuration files makes no sense on its own. In the end, we only need the |
af59166
to
fa86d7b
Compare
More changes have accumulated, must take another look
Well, what this PR just did is a breaking change from the behaviour POV without any warning. There probably was a reason we did this way in the past. It's true that mixing metadata is wrong, however, we allowed it and it also wasn't against the ecosystem practices, was it? (although very unexpected without a doubt). So this can't be compared to our recent dropping of Go vendoring flags, because those actually allowed projects to use incorrect repo setups which would not be buildable using standard toolkits the way users intended to in the first place, I'm not sure that's the case here. If we end up wanting this, then you'll have to accompany this change with a docs update (we'll also need to mention that in the release notes). That said, although I'm definitely not a fan of breaking backwards compatibility, strictly speaking SemVer [1]:
and so I won't stop this work based on this argument, but we'll probably need more voices in favour. [1] https://semver.org/#semantic-versioning-specification-semver |
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 still stuffed everything into commit 1. The changes can be introduced gradually by adding one unit test at a time and turning off that particular test area in the more complex unit test you're trying to kill. That way, you'd keep most of the things as is until you're ready to switch and then remove everything you don't need in a single commit, it can be done and the diff will be much more readable IMO. I'm not fond of trying to argument squashed changes by a complex unit test that isn't easily to be replaced (as I mentioned one option how to do it) as a justification - things can be made cleaner for the reader/reviewer.
@@ -460,14 +460,6 @@ def get_version(self) -> Optional[str]: | |||
log.warning("No project.version in pyproject.toml") | |||
return None | |||
|
|||
def check_dynamic_version(self) -> bool: |
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 don't explain anywhere why you're dropping this check.
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.
Can I use similar reasoning?
People should be aware of setup.py soft deprecation by now, do we want to hold everyone's hand? I mean displaying a warning for users who genuinely need setup.py (because pyproject.toml simply doesn't cut it for them as they may have C deps) doesn't feel right. I wouldn't strictly argue against having a warning if you proposed it somewhere in the code, I'm just questioning the usefulness given the circumstances.
Even though it is not deprecated. But the version is optional so I don't see much value in the warning message.
I'll try my best |
Also, it might be worth discussing setup.py as:
We can at least add a warning when extracting metadata from setup.py |
People should be aware of |
@brunoapimentel @a-ovchinnikov @taylormadore @ben-alkov any opinions on simpler yet backwards incompatible behaviour? |
Original behaviour looks somewhat strange to me -- I would think that a package which has name defined in one config, and version in another is malformed and will cause other issues as well. While possible I don't believe it is probable to find such a package. I am generally in favor of making a live test. The change breaks the original behavior, but I am not sure it was correct to begin with. With the code as we had it before we stopped at the first found pair of name and version, but technically every location could have defined its own name and version, so a sequence of
would have resulted in (foo, 1.0.0) and there is no good way of telling if it is the correct (name, version) rather than (baz, 2.3.4). Personally I would have rejected a package that has mismatching names or versions in its definition, or at least emitted a big warning. This change makes the code a little cleaner so I am in favor of it. |
1ef1462
to
b24a389
Compare
Signed-off-by: Michal Šoltis <[email protected]>
d64087f
to
84a0060
Compare
There is no context within the log warning. We don't warn users about other things when parsing package metadata (for example deprecation of setup.py). The version is an optional attribute in the SBOM. Even cachi2 uses "dynamic version". Signed-off-by: Michal Šoltis <[email protected]>
The commit follows the previous one, that drops a warning when processing metadata from pyproject.toml. This piece of code is no longer needed. Signed-off-by: Michal Šoltis <[email protected]>
Signed-off-by: Michal Šoltis <[email protected]>
…function Signed-off-by: Michal Šoltis <[email protected]>
Do not mix name and version from multiple config files (pyproject.toml, setup.cfg, setup.py) and with the name from git origin remote. Drastically simplify unit tests and speed up overall time while preserving the same coverage. Signed-off-by: Michal Šoltis <[email protected]>
84a0060
to
043ac2b
Compare
My local approximate results:
(venv) ~/cachi2 (main) $ time tox -e py312
(venv) ~/cachi2 (pip-refactoring) $ time tox -e py312
Maintainers will complete the following section
Note: if the contribution is external (not from an organization member), the CI
pipeline will not run automatically. After verifying that the CI is safe to run:
/ok-to-test
(as is the standard for Pipelines as Code)