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

Use dataclasses more liberally throughout the codebase #12571

Merged
merged 18 commits into from
Apr 18, 2024

Conversation

ichard26
Copy link
Member

This seemed like an easy refactoring. Please let me know if any/all of these conversions should be dropped.

Convert numerous internal classes to dataclasses for readability and stricter enforcement of immutability across the codebase. A conservative approach was taken in selecting which classes to convert. Classes which did not convert cleanly into a dataclass or were "too complex" (e.g. maintains interconnected state) were left alone.

Unfortunately due to dataclasses' poor handling of slots under 3.10,
converting the class to a dataclass is not an option. I would get
these errors which would be nontrivial to work around.

ValueError: '_hashes' in __slots__ conflicts with class variable

However, this is the last use of KeyBasedCompareMixin so after this
commit, it can be removed entirely!
I left ArchiveInfo alone as it has some non-trivial attribute logic that
I would rather not touch.
And drop a test that's now raising type errors as HiddenText can no
longer be compared with strings.
Comment on lines +68 to 69
@dataclass
class VcsInfo:
Copy link
Member Author

Choose a reason for hiding this comment

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

I opted to leave the direct_url classes non-frozen as some tests broke and it would be more consistent with ArchiveInfo which I did not convert (due to non-trivial logic I didn't feel like groking).

@@ -217,7 +218,7 @@ def resolve_revision(

if sha is not None:
rev_options = rev_options.make_new(sha)
Copy link
Member Author

Choose a reason for hiding this comment

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

RevOptions.make_new() seems like unnecessary boilerplate, but I've opted to leave additional (potential) refactoring to another PR.

Comment on lines +327 to 328
@dataclass
class CandidatePreferences:
Copy link
Member Author

Choose a reason for hiding this comment

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

The codebase does assign to these attributes from time to time...

Copy link
Member

Choose a reason for hiding this comment

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

sigh


SCHEME_KEYS = ["platlib", "purelib", "headers", "scripts", "data"]


@dataclass
Copy link
Member

Choose a reason for hiding this comment

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

Why can we not freeze this class?

Copy link
Member Author

Choose a reason for hiding this comment

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

if root is not None:
for key in SCHEME_KEYS:
value = change_root(root, getattr(scheme, key))
setattr(scheme, key, value)

Although this is trivial to fix... so I did that in 3738d25. Hopefully it doesn't break the test suite.

@@ -802,20 +802,6 @@ def test_basic(self) -> None:
assert hidden.redacted == "######"
assert hidden.secret == "my-secret"

def test_equality_with_str(self) -> None:
Copy link
Member

Choose a reason for hiding this comment

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

Hmm... I think this test should have been preserved.

Copy link
Member Author

Choose a reason for hiding this comment

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

The comparisons in this test are no longer type-safe which is why I removed it. I can adjust the class to support comparisons with strings if that's wanted.

mypy.....................................................................Failed
- hook id: mypy
- exit code: 1

tests/unit/test_utils.py:813: error: Non-overlapping equality check (left
operand type: "HiddenText", right operand type: "str")  [comparison-overlap]
            assert hidden != hidden.secret
                   ^~~~~~~~~~~~~~~~~~~~~~~
tests/unit/test_utils.py:814: error: Non-overlapping equality check (left
operand type: "str", right operand type: "HiddenText")  [comparison-overlap]
            assert hidden.secret != hidden
                   ^~~~~~~~~~~~~~~~~~~~~~~
tests/unit/test_utils.py:816: error: Non-overlapping equality check (left
operand type: "HiddenText", right operand type: "str")  [comparison-overlap]
            assert hidden != hidden.redacted
                   ^~~~~~~~~~~~~~~~~~~~~~~~~
tests/unit/test_utils.py:817: error: Non-overlapping equality check (left
operand type: "str", right operand type: "HiddenText")  [comparison-overlap]
            assert hidden.redacted != hidden
                   ^~~~~~~~~~~~~~~~~~~~~~~~~
Found 4 errors in 1 file (checked 292 source files)

Copy link
Member

@pfmoore pfmoore Apr 8, 2024

Choose a reason for hiding this comment

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

That's because you removed the __eq__ comparison from the HiddenText class... (Edit: More accurately, because the equality implementation added by the dataclass code doesn't have the same type signature as the one it replaced).

Copy link
Member Author

@ichard26 ichard26 Apr 8, 2024

Choose a reason for hiding this comment

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

It doesn't seem like the class was meant to be compared with strings in the first place (other than in tests) so it's debatable whether it's worth keeping it, but I've rolled this back in bde667d anyway.

# This is useful for testing.
def __eq__(self, other: Any) -> bool:
if type(self) != type(other):
return False
# The string being used for redaction doesn't also have to match,
# just the raw, original string.
return self.secret == other.secret

Edit: I've talked to some people more knowledgeable in typing than me and IIUC these type errors shouldn't be raised in the first place... Sometimes, I hate typing ¯\_(ツ)_/¯

@ichard26
Copy link
Member Author

Conflicts have been resolved.

@pradyunsg pradyunsg merged commit 4d09e3c into pypa:main Apr 18, 2024
24 checks passed
@sbidoul sbidoul mentioned this pull request Apr 21, 2024
@ichard26 ichard26 deleted the cleanup-dataclasses branch May 1, 2024 23:00
@github-actions github-actions bot locked as resolved and limited conversation to collaborators May 17, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants