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

Set properties on copied style before setting applicator #224

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

HalfWhitt
Copy link
Contributor

Fixes beeware/toga#2916

As discussed in beeware/toga#2919, the root cause wasn't in Toga, but in Travertino. Setting the applicator before assigning properties on the newly created duplicate style meant that those assignments were triggering apply, even though no implementation was yet available.

This passes both Travertino's tests as well as Toga's. CI will only verify the former, so double-checking my work on a local copy of the two together probably isn't a bad idea.

Speaking of which, that's something that seems to come up pretty often for me in working on what is primarily a subproject like this. I bet it would be complicated to set up some way to trigger running Toga's tests against a branch here, but it would be an extra bit of insurance... this bug would've been impossible to miss back in March.

PR Checklist:

  • All new features have been tested
  • All new features have been documented
  • I have read the CONTRIBUTING.md file
  • I will abide by the code of conduct

@HalfWhitt HalfWhitt changed the title Delay assigning applicator until after setting properties on copied s… Set properties on copied style before setting applicator Oct 18, 2024
@HalfWhitt
Copy link
Contributor Author

HalfWhitt commented Oct 18, 2024

Actually now that I think about it, is there a reason the applicator needs to be set in the copy method? Couldn't the caller just set the applicator on the copy after making it?

@HalfWhitt
Copy link
Contributor Author

HalfWhitt commented Oct 18, 2024

Just for some more context of what's happening when, the tests in test_apply.py all behave pretty similarly:

def test_set_default_right_textalign_when_rtl():
    root = ExampleNode("app", style=Pack(text_direction=RTL))
    root.style.reapply()
    # Two calls; one caused by text_align, one because text_direction
    # implies a change to text alignment.
    assert root._impl.set_alignment.mock_calls == [call(RIGHT), call(RIGHT)]

This works as expected because ExampleNode._impl isn't mocked until after it's called super.__init__():

class ExampleNode(Node):
    def __init__(self, name, style, size=None, children=None):
        super().__init__(
            style=style, children=children, applicator=TogaApplicator(self)
        )

        self.name = name
        self._impl = Mock()

If you move self._impl = Mock() to before super.__init(), the mock records an extra call(RIGHT); the intended two during reapply and an extra time first, before Node is finished initializing. The equivalent happens for all the other tests in the file.

This extra call — which in the case of Widget always fails because there's no _impl — is what the except in Travertino has been hiding all this time.

I still don't 100% understand the intended behavior of all the internals at play here, but the way these tests are written leads me to believe those early calls are not intended behavior, right? If they are supposed to be doing something, then yeah, _impl needs to be set sooner. Either that or like you mentioned, an apply afterward.

@freakboy3742
Copy link
Member

Actually now that I think about it, is there a reason the applicator needs to be set in the copy method? Couldn't the caller just set the applicator on the copy after making it?

I think the motivation to passing the applicator into the copy call was to ensure that the applicator is in place before the style values are set, so that all the properties being set are applied. This could also be achieved by applying all the values at the time the applicator is assigned - arguably, that would be a better approach, because at present, changing the applicator won't have the same effect as copying a style and passing in the new applicator.

I still don't 100% understand the intended behavior of all the internals at play here, but the way these tests are written leads me to believe those early calls are not intended behavior, right? If they are supposed to be doing something, then yeah, _impl needs to be set sooner. Either that or like you mentioned, an apply afterward.

If it makes you feel any better... I'm not sure I understand the intended behavior either... :-)

The current implementation definitely works... but it definitely appears that there's some implied behaviour and reliance on specific behaviours in Toga's usage of Travertino.

The core requirements are:

  1. An applicator is needed to apply a style to a node.
  2. We can't use an applicator fully until the node has been fully instiated.
  3. We can set values on the style before the applicator is set.
  4. We need all values of the style to be set using the applicator.

Trying to reverse engineer what I was thinking (mumble) years ago, I think I was trying to ensure that the applicator always existed, so you could always set properties on it, and then catch the consequences of an incomplete Node in the applicator. However, in retrospect, I think it might be better to use the existence of the applicator as the signal that a style can be applied, use the act of setting the applicator to apply the "initial" style, and then require that the node is ready to be applied onto at the time the applicator is assigned. Does that make sense?

Speaking of which, that's something that seems to come up pretty often for me in working on what is primarily a subproject like this. I bet it would be complicated to set up some way to trigger running Toga's tests against a branch here, but it would be an extra bit of insurance... this bug would've been impossible to miss back in March.

Agreed that a "canary" like this would be a worthwhile addition - since Toga is the main consumer of Travertino, it would be worth asserting that updates in Toga (or Travertino, for that matter), aren't going to break Toga on the next release.

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.

'ExampleWidget' object has no attribute '_impl' when using main branch Travertino
2 participants