Skip to content
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

bdist_wheel typing improvement #4383

Merged
merged 11 commits into from
Aug 8, 2024

Conversation

Avasam
Copy link
Contributor

@Avasam Avasam commented May 22, 2024

Summary of changes

A couple things I noticed from #4369 CC @agronholm

Pull Request Checklist

  • Changes have tests (existing tests + mypy checks newly completed methods)
  • News fragment added in newsfragments/.
    (See documentation for details)

@agronholm
Copy link
Contributor

Yeah, I didn't really focus on the type annotations because I feel that the whole things needs a rewrite, preferably on top of the packaging APIs I'm currently working on.

@Avasam Avasam force-pushed the bdist_wheel-typing-improvement branch 2 times, most recently from dd1637c to c44fb1e Compare May 23, 2024 14:47
@Avasam Avasam force-pushed the bdist_wheel-typing-improvement branch from c44fb1e to aa867ba Compare June 12, 2024 16:45
self.relative = False
self.owner = None
self.group = None
self.universal: bool = False
self.compression: str | int = "deflated"
self.compression: int = ZIP_DEFLATED
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Umm... that seems to be a runtime "lie".

We know for sure that self.compression will have an str value temporarily assigned to it (if the --compression flag is given in sys.argv).

If a developer believes this info, it may generate problems later?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By developer, do you mean someone working with bdist_wheel code?

I feel that this change is ill advised. If anything, we should use a separate member for the "translated" compression option.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By developer, do you mean someone working with bdist_wheel code?

Yes.

But I agree with you a better change would be separating into 2 different properties, or some arrangement similar to that.

Copy link
Contributor Author

@Avasam Avasam Jun 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like it intended to allow using a str as the initial value for human readability, that gets converted into its equivalent compression type int.

The problem is that you can set compression manually after finalize_options but before run, which would result in an error if you use a str. Hence compression cannot be a str* unless you expect the code using to to reconvert/recheck everytime.

The idea of allowing a string for human readability is kinda obsoleted by zipfile constants (ZIP_DEFLATED, ZIP_STORED, etc.).

Allowing a compression parameter as a string (like in __init__ ) is fine, because you can convert it in that method before it ever reached the compression attribute. So I kept that feature in.


* Whilst it can't be a str and have sane & safe typing, what it could be is a Descriptor (or property with setter):

  • setter that accepts both an int or str and does the conversion imediatly
  • getter that returns the int value

This would also move that logic out of finalize_options.

Copy link
Contributor

@abravalheri abravalheri Jun 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would also move that logic out of finalize_options.

I think that is fine... it is only used once and it is a relatively simple computation, so there is no need for caching... We can just defer until we actually need it.

Maybe something like the following:

diff --git i/setuptools/command/bdist_wheel.py w/setuptools/command/bdist_wheel.py
index 7bfb3fb02..97aed9428 100644
--- i/setuptools/command/bdist_wheel.py
+++ w/setuptools/command/bdist_wheel.py
@@ -248,7 +248,7 @@ class bdist_wheel(Command):
         self.owner = None
         self.group = None
         self.universal: bool = False
-        self.compression: int = ZIP_DEFLATED
+        self.compression: str | int = "deflated"
         self.python_tag: str = python_tag()
         self.build_number: str | None = None
         self.py_limited_api: str | Literal[False] = False
@@ -265,18 +265,6 @@ class bdist_wheel(Command):
         self.data_dir = self.wheel_dist_name + ".data"
         self.plat_name_supplied = bool(self.plat_name)
 
-        # Handle compression not being an int or a supported value
-        if not (
-            isinstance(self.compression, int)
-            and self.compression in self.supported_compressions.values()
-        ):
-            try:
-                self.compression = self.supported_compressions[str(self.compression)]
-            except KeyError:
-                raise ValueError(
-                    f"Unsupported compression: {self.compression}"
-                ) from None
-
         need_options = ("dist_dir", "plat_name", "skip_build")
 
         self.set_undefined_options("bdist", *zip(need_options, need_options))
@@ -304,6 +292,19 @@ class bdist_wheel(Command):
         if self.build_number is not None and not self.build_number[:1].isdigit():
             raise ValueError("Build tag (build-number) must start with a digit.")
 
+    def _zip_compression(self) -> int:
+        # Handle compression not being an int or a supported value
+        if (
+            isinstance(self.compression, int)
+            and self.compression in self.supported_compressions.values()
+        ):
+            return self.compression
+
+        try:
+            return self.supported_compressions[str(self.compression)]
+        except KeyError:
+            raise ValueError(f"Unsupported compression: {self.compression}") from None
+
     @property
     def wheel_dist_name(self) -> str:
         """Return distribution full name with - replaced with _"""
@@ -443,7 +444,7 @@ class bdist_wheel(Command):
             os.makedirs(self.dist_dir)
 
         wheel_path = os.path.join(self.dist_dir, archive_basename + ".whl")
-        with WheelFile(wheel_path, "w", self.compression) as wf:
+        with WheelFile(wheel_path, "w", self._zip_compression()) as wf:
             wf.write_files(archive_root)
 
         # Add to 'Distribution.dist_files' so that the "upload" command works

Copy link
Contributor Author

@Avasam Avasam Jun 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That works too. As you said, one difference with the Descriptor/property approach I was suggesting is caching the value.
Another difference is that the error will happen on usage rather than when setting an invalid value.
You still have to remember to use _zip_compression, and not compression directly

and self.compression in self.supported_compressions.values()
):
try:
self.compression = self.supported_compressions[str(self.compression)]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can see how the str(...) cast is deceiving the typechecker to make it happy: so it believes that self.compression is an integer in runtime... but we know it can actually be a string...

Copy link
Contributor Author

@Avasam Avasam Jun 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the idea of using a Descriptor or property would clean this up https://github.com/pypa/setuptools/pull/4383/files#r1646513416

setuptools/command/bdist_wheel.py Show resolved Hide resolved
Comment on lines 606 to 610
try:
if isinstance(self.compression, str):
return self.supported_compressions[self.compression]
except KeyError:
pass
Copy link
Contributor Author

@Avasam Avasam Jun 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be done w/o a try-except.

Suggested change
try:
if isinstance(self.compression, str):
return self.supported_compressions[self.compression]
except KeyError:
pass
if isinstance(self.compression, str):
compression = self.supported_compressions.get(self.compression)
if compression is not None:
return compression

And just for fun, using the walrus operator (Assignment Expressions, which I don't like)

Suggested change
try:
if isinstance(self.compression, str):
return self.supported_compressions[self.compression]
except KeyError:
pass
if isinstance(self.compression, str) and (
compression := self.supported_compressions.get(self.compression)
):
return compression

@Avasam Avasam requested a review from abravalheri July 1, 2024 16:23
mypy.ini Outdated Show resolved Hide resolved
@abravalheri abravalheri merged commit ab8290c into pypa:main Aug 8, 2024
19 of 21 checks passed
@abravalheri
Copy link
Contributor

Thank you very much!

@Avasam Avasam deleted the bdist_wheel-typing-improvement branch August 8, 2024 18:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants