-
Notifications
You must be signed in to change notification settings - Fork 14
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
Add an interface to turn objects read-only #1162
Conversation
Sub classes that use sentinel on __setattr__ need to special case when the attribute to set is `read_only` or `_read_only`. Failure to do so would result in being unable to unlock the object. `sentinel` now handles this. Additionally adds a `lock_method`. Default is `error`, but can be set to emit a warning and allow modification instead.
This requires a few supers where subclasses were previously not diligent about it
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. |
Current failure seems to be related to #1200 |
Functionality and documentation is done from my side. There's some tests as this is now used by |
@pmrv, this hasn't escaped my notice, I just didn't manage to get this far today! |
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.
Really nice, Marvin. For specific application here, I only have the one nit to refactor:rename unlocked()
to unlock()
. Otherwise lgtm, tests can be written, and I think this is useful in base.
Zooming out a bit though, I think this is an elegant solution to the very generic problem of transforming python attributes to being read-only. I think this would be an excellent candidate for the standard-library-only toolbox discussed in #1208. Here it would just be a matter of purging reference to HasGroups
, which could then be re-introduced here in pyiron_base
by defining _iterate_lockable_subs
and a new class inheriting from the generic tool that overrides _on_lock
and _on_unlock
. Unlike Singleton
, which is a breeze to re-implement as needed, what you have here is a bit more complex and might really be useful to the community as a standalone tool (or at least stand-with-other-lightweight-tools-in-a-nice-toolbox)
object.__setattr__(self, "_lock_method", method) | ||
self.read_only = True | ||
|
||
def unlocked(self) -> _UnlockContext: |
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.
nit: I think this should be unlock
to match the verb tense of lock
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 named it asymmetrical on purpose, because lock
and unlocked
are not symmetric either.
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.
What I wanted to avoid is that some code unlocks an object and then crashes (or forgets it) before it can relock it. So obj.unlocked()
is a context manager that does the relocking.
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.
Clever. I approve. But I was too dense to pick up on it on the first read, so maybe a comment or docstring to clarify intent is good.
for it in _iterate_lockable_subs(self): | ||
it.lock() |
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.
for it in _iterate_lockable_subs(self): | |
it.lock() | |
pass |
maybe (see main review comment)
for it in _iterate_lockable_subs(self): | ||
it.read_only = 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.
for it in _iterate_lockable_subs(self): | |
it.read_only = False | |
pass |
maybe (see main review comment)
def _iterate_lockable_subs(lockable_groups): | ||
""" | ||
Yield sub nodes and groups that are lockable. Recurse into groups. | ||
|
||
If the given object is not a :class:`HasGroups` yield nothing. | ||
""" | ||
if not isinstance(lockable_groups, HasGroups): | ||
return | ||
|
||
subs = lockable_groups.list_all() | ||
for n in subs["nodes"]: | ||
node = lockable_groups[n] | ||
if isinstance(node, Lockable): | ||
yield node | ||
|
||
for g in subs["groups"]: | ||
group = lockable_groups[g] | ||
yield group | ||
yield from _iterate_lockable_subs(g) |
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.
pyiron_base-specific
Integration tests are currently not passing, because base wants pint==0.23, but atomistics still requires <=0.22. I assume this will solve itself when atomistics is updated, but will go ahead merge this here anyway. |
It seems like there was more wrong than just the |
As we discussed in #1157 it be nice to have some way of blocking users from changing values or calling methods after a certain point in time. This is implemented here with some doc test examples. For now it locks this recursively, but I have a vague fear that this might be not be super performant. I'll have to do some tests. I'll also move over at least
DataContainer
andServer
in this PR.@liamhuber Do you remember why
HasStoredTraits
implements its ownread_only
logic instead of reusing the existing one fromDataContainer
?