Skip to content

Commit

Permalink
Merge pull request beeware#2686 from KRRT7/lazy_imports_2
Browse files Browse the repository at this point in the history
implement lazy loading in toga_core
  • Loading branch information
freakboy3742 authored Oct 28, 2024
2 parents 7367a95 + 30ccb62 commit 18d6559
Show file tree
Hide file tree
Showing 7 changed files with 136 additions and 130 deletions.
4 changes: 2 additions & 2 deletions android/src/toga_android/widgets/slider.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from android.widget import SeekBar
from java import dynamic_proxy

import toga
from toga.widgets.slider import IntSliderImpl

from .base import Widget

Expand All @@ -31,7 +31,7 @@ def onStopTrackingTouch(self, native_seekbar):
self.impl.interface.on_release()


class Slider(Widget, toga.widgets.slider.IntSliderImpl):
class Slider(Widget, IntSliderImpl):
focusable = False
TICK_DRAWABLE = None

Expand Down
1 change: 1 addition & 0 deletions changes/2547.misc.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Imports from the ``toga`` core namespace has been modified to use lazy importing.
4 changes: 2 additions & 2 deletions cocoa/src/toga_cocoa/widgets/slider.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from travertino.size import at_least

import toga
from toga.widgets.slider import SliderImpl
from toga_cocoa.libs import (
SEL,
NSEventType,
Expand All @@ -27,7 +27,7 @@ def onSlide_(self, sender) -> None:
self.interface.on_change()


class Slider(Widget, toga.widgets.slider.SliderImpl):
class Slider(Widget, SliderImpl):
def create(self):
self.native = TogaSlider.alloc().init()
self.native.interface = self.interface
Expand Down
211 changes: 89 additions & 122 deletions core/src/toga/__init__.py
Original file line number Diff line number Diff line change
@@ -1,55 +1,97 @@
from __future__ import annotations

import importlib
import warnings
from pathlib import Path

from .app import App, DocumentApp
from .colors import hsl, hsla, rgb, rgba
from .command import Command, Group
from .dialogs import (
ConfirmDialog,
ErrorDialog,
InfoDialog,
OpenFileDialog,
QuestionDialog,
SaveFileDialog,
SelectFolderDialog,
StackTraceDialog,
)
from .documents import Document, DocumentWindow
from .fonts import Font
from .icons import Icon
from .images import Image
from .keys import Key
from .statusicons import MenuStatusIcon, SimpleStatusIcon
from .types import LatLng, Position, Size
from .widgets.activityindicator import ActivityIndicator
from .widgets.base import Widget
from .widgets.box import Box
from .widgets.button import Button
from .widgets.canvas import Canvas
from .widgets.dateinput import DateInput, DatePicker
from .widgets.detailedlist import DetailedList
from .widgets.divider import Divider
from .widgets.imageview import ImageView
from .widgets.label import Label
from .widgets.mapview import MapPin, MapView
from .widgets.multilinetextinput import MultilineTextInput
from .widgets.numberinput import NumberInput
from .widgets.optioncontainer import OptionContainer, OptionItem
from .widgets.passwordinput import PasswordInput
from .widgets.progressbar import ProgressBar
from .widgets.scrollcontainer import ScrollContainer
from .widgets.selection import Selection
from .widgets.slider import Slider
from .widgets.splitcontainer import SplitContainer
from .widgets.switch import Switch
from .widgets.table import Table
from .widgets.textinput import TextInput
from .widgets.timeinput import TimeInput, TimePicker
from .widgets.tree import Tree
from .widgets.webview import WebView
from .window import MainWindow, Window
toga_core_imports = {
# toga.app imports
"App": "toga.app",
"DocumentApp": "toga.app",
# toga.colors imports
"hsl": "toga.colors",
"hsla": "toga.colors",
"rgb": "toga.colors",
"rgba": "toga.colors",
# toga.command imports
"Command": "toga.command",
"Group": "toga.command",
# toga.dialogs imports
"ConfirmDialog": "toga.dialogs",
"ErrorDialog": "toga.dialogs",
"InfoDialog": "toga.dialogs",
"OpenFileDialog": "toga.dialogs",
"QuestionDialog": "toga.dialogs",
"SaveFileDialog": "toga.dialogs",
"SelectFolderDialog": "toga.dialogs",
"StackTraceDialog": "toga.dialogs",
# toga.documents imports
"Document": "toga.documents",
"DocumentWindow": "toga.documents",
# toga.fonts imports
"Font": "toga.fonts",
# toga.icons imports
"Icon": "toga.icons",
# toga.images imports
"Image": "toga.images",
# toga.keys imports
"Key": "toga.keys",
# toga.statusicons imports
"MenuStatusIcon": "toga.statusicons",
"SimpleStatusIcon": "toga.statusicons",
# toga.types imports
"LatLng": "toga.types",
"Position": "toga.types",
"Size": "toga.types",
# toga.widgets imports
"ActivityIndicator": "toga.widgets.activityindicator",
"Widget": "toga.widgets.base",
"Box": "toga.widgets.box",
"Button": "toga.widgets.button",
"Canvas": "toga.widgets.canvas",
"DateInput": "toga.widgets.dateinput",
"DatePicker": "toga.widgets.dateinput",
"DetailedList": "toga.widgets.detailedlist",
"Divider": "toga.widgets.divider",
"ImageView": "toga.widgets.imageview",
"Label": "toga.widgets.label",
"MapPin": "toga.widgets.mapview",
"MapView": "toga.widgets.mapview",
"MultilineTextInput": "toga.widgets.multilinetextinput",
"NumberInput": "toga.widgets.numberinput",
"OptionContainer": "toga.widgets.optioncontainer",
"OptionItem": "toga.widgets.optioncontainer",
"PasswordInput": "toga.widgets.passwordinput",
"ProgressBar": "toga.widgets.progressbar",
"ScrollContainer": "toga.widgets.scrollcontainer",
"Selection": "toga.widgets.selection",
"Slider": "toga.widgets.slider",
"SplitContainer": "toga.widgets.splitcontainer",
"Switch": "toga.widgets.switch",
"Table": "toga.widgets.table",
"TextInput": "toga.widgets.textinput",
"TimeInput": "toga.widgets.timeinput",
"TimePicker": "toga.widgets.timeinput",
"Tree": "toga.widgets.tree",
"WebView": "toga.widgets.webview",
# toga.window imports
"DocumentMainWindow": "toga.window",
"MainWindow": "toga.window",
"Window": "toga.window",
}
__all__ = list(toga_core_imports.keys())


def __getattr__(name):
try:
module_name = toga_core_imports[name]
except KeyError:
raise AttributeError(f"module '{__name__}' has no attribute '{name}'") from None
else:
module = importlib.import_module(module_name)
value = getattr(module, name)
globals()[name] = value
return value


class NotImplementedWarning(RuntimeWarning):
Expand All @@ -62,81 +104,6 @@ def warn(cls, platform: str, feature: str) -> None:
warnings.warn(NotImplementedWarning(f"[{platform}] Not implemented: {feature}"))


__all__ = [
"NotImplementedWarning",
# Applications
"App",
"DocumentApp",
# Commands
"Command",
"Group",
# Documents
"Document",
"DocumentWindow",
# Dialogs
"ConfirmDialog",
"ErrorDialog",
"InfoDialog",
"OpenFileDialog",
"QuestionDialog",
"SaveFileDialog",
"SelectFolderDialog",
"StackTraceDialog",
# Keys
"Key",
# Resources
"hsl",
"hsla",
"rgb",
"rgba",
"Font",
"Icon",
"Image",
# Status icons
"MenuStatusIcon",
"SimpleStatusIcon",
# Types
"LatLng",
"Position",
"Size",
# Widgets
"ActivityIndicator",
"Box",
"Button",
"Canvas",
"DateInput",
"DetailedList",
"Divider",
"ImageView",
"Label",
"MapPin",
"MapView",
"MultilineTextInput",
"NumberInput",
"OptionContainer",
"OptionItem",
"PasswordInput",
"ProgressBar",
"ScrollContainer",
"Selection",
"Slider",
"SplitContainer",
"Switch",
"Table",
"TextInput",
"TimeInput",
"Tree",
"WebView",
"Widget",
# Windows
"MainWindow",
"Window",
# Deprecated widget names
"DatePicker",
"TimePicker",
]


def _package_version(file: Path | str | None, name: str) -> str:
try:
# Read version from SCM metadata
Expand Down
38 changes: 38 additions & 0 deletions core/tests/test_import.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import sys

import pytest


def test_lazy_succeed(monkeypatch):
"""Submodules are imported on demand."""
for mod_name in ["toga", "toga.documents", "toga.widgets.button"]:
monkeypatch.delitem(sys.modules, mod_name, raising=False)

# A clean import of the top-level toga module should not import any submodules.
import toga

assert "toga.documents" not in sys.modules
assert "toga.widgets.button" not in sys.modules

# Accessing a name should import only the necessary submodules.
Button = toga.Button
assert "toga.widgets.button" in sys.modules
assert "toga.documents" not in sys.modules

# Accessing a name multiple times should return the same object.
assert Button is toga.Button
assert Button is sys.modules["toga.widgets.button"].Button

# Same again with a different module.
Document = toga.Document
assert Document is sys.modules["toga.documents"].Document


def test_lazy_fail():
"""Nonexistent names should raise a normal AttributeError."""
import toga

with pytest.raises(
AttributeError, match="module 'toga' has no attribute 'nonexistent'"
):
toga.nonexistent
4 changes: 2 additions & 2 deletions dummy/src/toga_dummy/widgets/slider.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import toga
from toga.widgets.slider import SliderImpl

from .base import Widget


class Slider(Widget, toga.widgets.slider.SliderImpl):
class Slider(Widget, SliderImpl):
def create(self):
self._action("create Slider")

Expand Down
4 changes: 2 additions & 2 deletions gtk/src/toga_gtk/widgets/slider.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from travertino.size import at_least

import toga
from toga.widgets.slider import SliderImpl

from ..libs import Gtk
from .base import Widget
Expand All @@ -16,7 +16,7 @@
# to line up at the same values.


class Slider(Widget, toga.widgets.slider.SliderImpl):
class Slider(Widget, SliderImpl):
def create(self):
self.adj = Gtk.Adjustment()
self.native = Gtk.Scale.new(Gtk.Orientation.HORIZONTAL, self.adj)
Expand Down

0 comments on commit 18d6559

Please sign in to comment.