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

[widget Audit] toga.App #2075

Merged
merged 74 commits into from Oct 31, 2023
Merged

[widget Audit] toga.App #2075

merged 74 commits into from Oct 31, 2023

Conversation

freakboy3742
Copy link
Member

@freakboy3742 freakboy3742 commented Aug 11, 2023

Audit of App, DocumentApp, Document and Command

Builds on #2058.

  • Document and DocumentApp has been overhauled. Document now uses path rather than filename; and Documents are now expected to provide a create() method that assigns main_window as part of their creation process.
  • App.exit() now exits unconditionally, rather than triggering "can I close" logic. This matches the behavior of Window.close()
  • CommandSet, GROUP_BREAK and SECTION_BREAK have been removed from the toga base namespace. These classes shouldn't be needed by end users; they're internal details.

Fixes:

Audit checklist

  • Core tests ported to pytest
  • Core tests at 100% coverage
  • Core docstrings complete, and in Sphinx format
  • Documentation complete and consistently formatted
  • cocoa backend at 100% coverage
  • winforms backend at 100% coverage
  • gtk backend at 100% coverage
  • iOS backend at 100% coverage
  • android backend at 100% coverage
  • Widget support table updated
  • Widget Issue backlog reviewed

@freakboy3742 freakboy3742 force-pushed the audit-app branch 2 times, most recently from 3ca628a to 6a7a7fd Compare August 17, 2023 12:46
@mhsmith
Copy link
Member

mhsmith commented Oct 29, 2023

The missing coverage on toga_winforms\app.py line 83 should be fixed once beeware/briefcase-windows-VisualStudio-template#23 has been merged, and propagated to the stub EXEs in the windows-app template.

@mhsmith
Copy link
Member

mhsmith commented Oct 29, 2023

With the last commit, macOS is crashing consistently in CI after the third test. I can't reproduce the problem locally, and the changes since the last time it passed don't contain anything that could affect macOS, other than a change to a test which it never got remotely close to running.

def __isub__(self, other: Window) -> None:
self.discard(other)
return self

Copy link
Member Author

Choose a reason for hiding this comment

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

Although they're no longer required, there will be enough code in existence that uses these methods that I don't think we can delete them completely. Toga's own examples contain a bunch of uses of this.

At the very least, they should exist, but be no-ops that log DeprecationWarnings.

Copy link
Member

@mhsmith mhsmith Oct 30, 2023

Choose a reason for hiding this comment

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

OK, I've restored them and added warnings, since they were never consistent with the standard set interface anyway.

I could only find one remaining usage in the examples, which I see you've already removed in #2160.

raise ValueError(
"The `windows` constructor argument of toga.App has been removed. "
"Windows are now automatically added to the current app."
)
Copy link
Member Author

Choose a reason for hiding this comment

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

Why a full exception? Elsewhere we've logged a DeprecationWarning and transformed the input as best we can.

Copy link
Member

Choose a reason for hiding this comment

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

As part of the Window audit, I changed it so that Windows are added to the App when they're created, instead of when they're shown. Then as part of this PR, I added a check to make sure that the App is created before the Window:

self._app = None
if App.app is None:
raise RuntimeError("Cannot create a Window before creating an App")
App.app.windows.add(self)

I appreciate that this may break some existing code, but I think it's worth it because it makes it significantly easier to reason about the relationship between App and Window, both for us and for the users. Anyone who's been following the style of the Toga examples will not be affected.

This means that any windows passed to the App constructor must already have been added to a different App. so there's no way we can handle that situation sensibly.

core/src/toga/app.py Outdated Show resolved Hide resolved
"""The name of the distribution used to load metadata with
:any:`importlib.metadata` (read-only)."""
return self._app_name
return self._distribution_name
Copy link
Member Author

Choose a reason for hiding this comment

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

On reflection: I'm not 100% sure this deprecation/rename is warranted.

The name->formal_name is definitely called for; name is ambiguous, especially given the usage in PEP621 context.

However, I don't think renaming app_name->distribution_name is as much of a win.

  • PEP621 talks about "distribution names" as a noun, but doesn't actually use distribution_name anywhere. There are APIs to canonicalize_name() and normalize_name(), which reference the Name metadata field.
  • Most of PEP621 is focussed on distributing libraries to PyPI, not "apps" in the Briefcase sense.
  • The docs for Name call it the "distribution name"; I think you could reasonably argue that the "distribution" in any Toga/Briefcase context is an "app"
  • Briefcase uses app_name extensively, so we'd be facing an inconsistency that we'd need to resolve.
  • app_name isn't ambiguous, and is easy enough to document as "the distribution name".
  • It's code churn for existing uses, and the new name is longer.

In short, it's a disruptive name change that has ongoing consequences on Briefcase, but isn't actually converging on a specific usage that the broader Python ecosystem uses.

Replacing app_name with name at some point in the future would make some sense, as that would be converging on PEP621 usage/naming - but I don't think the interim name change actually helps that much.

Copy link
Member

@mhsmith mhsmith Oct 30, 2023

Choose a reason for hiding this comment

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

OK, that makes sense; I'll revert the rename.

Things will be less confusing once we remove the deprecated name property, so I'll make an issue to remind us to do that (#2212).

docs/reference/api/resources/command.rst Outdated Show resolved Hide resolved
@@ -135,7 +136,7 @@ def action4(widget):

# The order in which commands are added to the app or the toolbar won't
# alter anything. Ordering is defined by the command definitions.
self.app.commands.add(cmd1, cmd0, cmd6, cmd4, cmd3)
self.app.commands.add(cmd1, cmd0, cmd6, cmd4, cmd3, cmd7)
Copy link
Member Author

Choose a reason for hiding this comment

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

Not adding cmd7 here was an explicit test of the "adding to toolbar implies adding to app" case.

Copy link
Member

Choose a reason for hiding this comment

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

Reverted.

Copy link
Member

@mhsmith mhsmith Oct 31, 2023

Choose a reason for hiding this comment

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

On second thoughts, I think it's worth keeping this to verify that adding a command to both places doesn't cause any problems. And I've added a similar case to the core tests.

Copy link
Member Author

Choose a reason for hiding this comment

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

As long as we've got an example of all three possibilities (app only, toolbar only, app and toolbar), 👍.

main_window.hide()
assert app.current_window is None
assert app.current_window in [None, main_window]
Copy link
Member Author

Choose a reason for hiding this comment

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

We can definitely live with this for now, but it would be worth logging as a bug that should be possible to fix.

@mhsmith
Copy link
Member

mhsmith commented Oct 30, 2023

The last commit has caused failures on Windows and macOS. I'm currently looking into the Windows one, but I haven't started on macOS.

def __iadd__(self, window: Window) -> None:
# The standard set type does not have a += operator.
warn("Instead of +=, use add()", DeprecationWarning, stacklevel=2)
self.add(window)
Copy link
Member Author

Choose a reason for hiding this comment

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

Since any user-space usage of += or -= is going to be fighting against Toga's internal window management, my inclination is to make these two no-ops - retain the API, but raise a warning that leads the user to you don't need to do this at all, rather than suggesting they need to use a different API name.

@mhsmith mhsmith merged commit 0b7b091 into beeware:main Oct 31, 2023
35 checks passed
@freakboy3742 freakboy3742 deleted the audit-app branch October 31, 2023 21:56
@freakboy3742 freakboy3742 mentioned this pull request Nov 1, 2023
42 tasks
@mhsmith
Copy link
Member

mhsmith commented Nov 6, 2023

The last commit has caused failures on Windows and macOS.

For future reference, the Windows failure was a memory leak detected by test_secondary_window_cleanup. It was fixed as a byproduct of moving the initialization of CommandSet.app to the CommandSet constructor, but I've tracked down the original cause as pythonnet/pythonnet#2279, which could hit us in all kinds of places.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment