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
DEPR: deprecate direct mutations of TableElement.fields
by making it an owned list
#16316
base: main
Are you sure you want to change the base?
DEPR: deprecate direct mutations of TableElement.fields
by making it an owned list
#16316
Conversation
Thank you for your contribution to Astropy! 🌌 This checklist is meant to remind the package maintainers who will review this pull request of some common things to look for.
|
👋 Thank you for your draft pull request! Do you know that you can use |
1421f5f
to
fef1d87
Compare
fef1d87
to
27d3bca
Compare
TableElement.fields
by making it an owned list
464abe7
to
171dcc5
Compare
return super().__setitem__(key, value) | ||
|
||
@_locked_mutator | ||
def __delitem__(self, key: SupportsIndex | slice) -> 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.
A note on type annotations: I didn't intend to add them, but got them for free from my editor's supercharged auto complete.
astropy/utils/collections.py
Outdated
return inner | ||
|
||
|
||
class _OwnedList(list): |
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's not intended as a new feature, which is why I'm making it private by default, but I could just as easily make this class public, or move it to a different module, closer to where it's used.
@@ -819,40 +819,41 @@ def test_select_columns_binary(format_): | |||
assert table.colnames == ["string_test", "string_test_2", "unicode_test"] | |||
|
|||
|
|||
def table_from_scratch(): | |||
def test_direct_fields_mutation(): |
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.
here the diff looks fishy because it's collapsing 2 independent changes
- I added a new test whose preamble is based on
table_from_scratch
- I removed
table_from_scratch
when I found that it wasn't used anywhere
I could split this cleanup into its own PR if desired.
@@ -2658,13 +2674,12 @@ def _resize_strategy(self, size): | |||
return int(np.ceil(size * RESIZE_AMOUNT)) | |||
|
|||
def add_field(self, field: Field) -> None: | |||
self.fields.append(field) | |||
self.all_fields.append(field) |
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 found that this line was not needed and in fact caused some bugs that are revealed by existing tests once we start using add_field
instead of direct mutations. This is a regression from #15959, which can easily be fixed independently in a -1/+0 separate PR if desired.
d4716b1
to
990c546
Compare
990c546
to
d672492
Compare
d672492
to
dae25d7
Compare
Having a subclass that must disable a large number of its inherited methods doesn't look right. Have you considered implementing |
Unfortunately mutability is still needed internally, so I don't think a tuple would suitably replace the existing data structure. |
I'm not sure what the best solution here would be, but disabling this many methods makes it apparent that you don't want this to be a |
I still want it to be a list, I just don't want it mutated by anyone who's not the container object. |
So you don't want it to be a list. |
Well to take a different angle, we're talking about an attribute that's both a list (mutable) and a property (read-only), which one could argue is already a contradiction, but that ship sailed long ago. I don't think however that it's fundamentally contradictory, the language just doesn't offer a simple way to build such properties. |
Couldn't you have a private |
Whatever we do needs to go through a gradual deprecation process. We can't make public API private or broken in one go. |
A vanilla |
I think I see your point. However it doesn't feel right to me that the proposed intermediate step involves a "mutable tuple" of sorts. I also think such a class would end up looking a lot like |
The important thing for me is that we don't end up with a permanent immutable |
ok then I'll add a note to #16314 and rework the warning message here so that it avoids making promises about which kind of error will replace it in the future. |
@tomdonaldson I think you're in a great position to review this, but no rush, I'm just letting you know :) |
Description
Fixes #16314
The deprecation process is quite involved since we need to raise a warning for external usage of, say,
table.fields.append
, but still allow this operation to be performed silently internally.My solution is to make
TableElement.fields
a "owned list" (my wording), that is, a list that has an owner object (namely theTableElement
instance it belongs to), which is the only one allowed to directly mutate it (using a private keyword argument_owned=True
). Mutations from other actors are still allowed as a temporary measure, but now raise aDeprecationWarning
.The commit history is meant to allow reviewing my process step by step. I'll also add some comments to the diff if I can help anticipate some questions from reviewers.
TODO
__iadd__
,__imul__
,__setitem__
and__delitem__
)_OwnedList
and_OwnedHomegeneousList
(inheritance order matters !)fields
property immutable #16314 opened while the deprecation is on-going ?)