oe-python-template 0.14.3__tar.gz → 0.14.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. {oe_python_template-0.14.3 → oe_python_template-0.14.4}/.gitignore +2 -1
  2. {oe_python_template-0.14.3 → oe_python_template-0.14.4}/PKG-INFO +1 -1
  3. {oe_python_template-0.14.3 → oe_python_template-0.14.4}/pyproject.toml +3 -2
  4. {oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/api.py +7 -6
  5. {oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/hello/_api.py +3 -3
  6. {oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/hello/_gui.py +5 -3
  7. {oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/system/_api.py +1 -1
  8. {oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/system/_gui.py +2 -2
  9. {oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/utils/__init__.py +2 -1
  10. oe_python_template-0.14.4/src/oe_python_template/utils/_api.py +65 -0
  11. {oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/utils/_di.py +6 -0
  12. oe_python_template-0.14.4/src/oe_python_template/utils/_gui.py +212 -0
  13. {oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/utils/_notebook.py +12 -7
  14. oe_python_template-0.14.3/src/oe_python_template/utils/_api.py +0 -18
  15. oe_python_template-0.14.3/src/oe_python_template/utils/_gui.py +0 -178
  16. {oe_python_template-0.14.3 → oe_python_template-0.14.4}/LICENSE +0 -0
  17. {oe_python_template-0.14.3 → oe_python_template-0.14.4}/README.md +0 -0
  18. {oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/__init__.py +0 -0
  19. {oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/cli.py +0 -0
  20. {oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/constants.py +0 -0
  21. {oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/hello/__init__.py +0 -0
  22. {oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/hello/_cli.py +0 -0
  23. {oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/hello/_constants.py +0 -0
  24. {oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/hello/_models.py +0 -0
  25. {oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/hello/_service.py +0 -0
  26. {oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/hello/_settings.py +0 -0
  27. {oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/system/__init__.py +0 -0
  28. {oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/system/_cli.py +0 -0
  29. {oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/system/_service.py +0 -0
  30. {oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/system/_settings.py +0 -0
  31. {oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/utils/.vendored/bottle.py +0 -0
  32. {oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/utils/_cli.py +0 -0
  33. {oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/utils/_console.py +0 -0
  34. {oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/utils/_constants.py +0 -0
  35. {oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/utils/_health.py +0 -0
  36. {oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/utils/_log.py +0 -0
  37. {oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/utils/_logfire.py +0 -0
  38. {oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/utils/_process.py +0 -0
  39. {oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/utils/_sentry.py +0 -0
  40. {oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/utils/_service.py +0 -0
  41. {oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/utils/_settings.py +0 -0
  42. {oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/utils/boot.py +0 -0
@@ -81,7 +81,8 @@ node_modules/
81
81
  *.rej
82
82
 
83
83
  # Scalene
84
- .profile
84
+ profile.json
85
+ profile.html
85
86
 
86
87
 
87
88
  # Vercel
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: oe-python-template
3
- Version: 0.14.3
3
+ Version: 0.14.4
4
4
  Summary: 🧠 Copier template to scaffold Python projects compliant with best practices and modern tooling.
5
5
  Project-URL: Homepage, https://oe-python-template.readthedocs.io/en/latest/
6
6
  Project-URL: Documentation, https://oe-python-template.readthedocs.io/en/latest/
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "oe-python-template"
3
- version = "0.14.3"
3
+ version = "0.14.4"
4
4
  description = "🧠 Copier template to scaffold Python projects compliant with best practices and modern tooling."
5
5
  readme = "README.md"
6
6
  authors = [{ name = "Helmut Hoffer von Ankershoffen", email = "helmuthva@gmail.com" }]
@@ -118,6 +118,7 @@ dev = [
118
118
  "pytest-watcher>=0.4.3",
119
119
  "pytest-xdist[psutil]>=3.6.1",
120
120
  "ruff>=0.11.6",
121
+ "scalene>=1.5.51",
121
122
  "sphinx>=8.2.3",
122
123
  "sphinx-autobuild>=2024.10.3",
123
124
  "sphinx-copybutton>=0.5.2",
@@ -279,7 +280,7 @@ source = ["src/"]
279
280
 
280
281
 
281
282
  [tool.bumpversion]
282
- current_version = "0.14.3"
283
+ current_version = "0.14.4"
283
284
  parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)"
284
285
  serialize = ["{major}.{minor}.{patch}"]
285
286
  search = "{current_version}"
@@ -16,7 +16,7 @@ from .utils import (
16
16
  __base__url__,
17
17
  __documentation__url__,
18
18
  __repository_url__,
19
- locate_implementations,
19
+ load_modules,
20
20
  )
21
21
 
22
22
  TITLE = "OE Python Template"
@@ -67,12 +67,13 @@ for version, semver in API_VERSIONS.items():
67
67
  terms_of_service=TERMS_OF_SERVICE_URL,
68
68
  )
69
69
 
70
- # Register routers with appropriate API versions
71
- for router in locate_implementations(VersionedAPIRouter):
72
- router_instance: VersionedAPIRouter = router
73
- version = router_instance.version
70
+ load_modules()
71
+
72
+ # Register routers with appropriate API versions using the tracked instances
73
+ for router in VersionedAPIRouter.get_instances():
74
+ version = router.version # type: ignore
74
75
  if version in API_VERSIONS:
75
- api_instances[version].include_router(router_instance)
76
+ api_instances[version].include_router(router) # type: ignore
76
77
 
77
78
  # Mount all API versions to the main app
78
79
  for version in API_VERSIONS:
@@ -8,7 +8,7 @@ This module provides a webservice API with several operations:
8
8
  from collections.abc import Generator
9
9
  from typing import Annotated
10
10
 
11
- from fastapi import Depends
11
+ from fastapi import APIRouter, Depends
12
12
  from pydantic import BaseModel, Field
13
13
 
14
14
  from oe_python_template.utils import VersionedAPIRouter
@@ -20,8 +20,8 @@ HELLO_WORLD_EXAMPLE = "Hello, world!"
20
20
 
21
21
  # VersionedAPIRouters exported by modules via their __init__.py are automatically registered
22
22
  # and injected into the main API app, see ../api.py.
23
- api_v1 = VersionedAPIRouter("v1", prefix="/hello", tags=["hello"])
24
- api_v2 = VersionedAPIRouter("v2", prefix="/hello", tags=["hello"])
23
+ api_v1: APIRouter = VersionedAPIRouter("v1", prefix="/hello", tags=["hello"]) # type: ignore
24
+ api_v2: APIRouter = VersionedAPIRouter("v2", prefix="/hello", tags=["hello"]) # type: ignore
25
25
 
26
26
 
27
27
  def get_service() -> Generator[Service, None, None]:
@@ -2,8 +2,6 @@
2
2
 
3
3
  from pathlib import Path
4
4
 
5
- from nicegui import ui
6
-
7
5
  from oe_python_template.utils import BasePageBuilder, GUILocalFilePicker
8
6
 
9
7
  from ._service import Service
@@ -11,13 +9,17 @@ from ._service import Service
11
9
 
12
10
  async def pick_file() -> None:
13
11
  """Open a file picker dialog and show notifier when closed again."""
14
- result = await GUILocalFilePicker(str(Path.cwd() / "examples"), multiple=True)
12
+ from nicegui import ui # noqa: PLC0415
13
+
14
+ result = await GUILocalFilePicker(str(Path.cwd() / "examples"), multiple=True) # type: ignore
15
15
  ui.notify(f"You chose {result}")
16
16
 
17
17
 
18
18
  class PageBuilder(BasePageBuilder):
19
19
  @staticmethod
20
20
  def register_pages() -> None:
21
+ from nicegui import ui # noqa: PLC0415
22
+
21
23
  @ui.page("/")
22
24
  def page_index() -> None:
23
25
  """Homepage of GUI."""
@@ -110,7 +110,7 @@ def register_info_endpoint(router: APIRouter) -> Callable[..., dict[str, Any]]:
110
110
 
111
111
  api_routers = {}
112
112
  for version in API_VERSIONS:
113
- router = VersionedAPIRouter(version, tags=["system"])
113
+ router: APIRouter = VersionedAPIRouter(version, tags=["system"]) # type: ignore
114
114
  api_routers[version] = router
115
115
  health = register_health_endpoint(api_routers[version])
116
116
  info = register_info_endpoint(api_routers[version])
@@ -1,7 +1,5 @@
1
1
  """Homepage (index) of GUI."""
2
2
 
3
- from nicegui import ui
4
-
5
3
  from ..utils import BasePageBuilder, __project_name__, __version__ # noqa: TID252
6
4
  from ._service import Service
7
5
 
@@ -9,6 +7,8 @@ from ._service import Service
9
7
  class PageBuilder(BasePageBuilder):
10
8
  @staticmethod
11
9
  def register_pages() -> None:
10
+ from nicegui import ui # noqa: PLC0415
11
+
12
12
  @ui.page("/info")
13
13
  def page_info() -> None:
14
14
  """Homepage of GUI."""
@@ -18,7 +18,7 @@ from ._constants import (
18
18
  __repository_url__,
19
19
  __version__,
20
20
  )
21
- from ._di import locate_implementations, locate_subclasses
21
+ from ._di import load_modules, locate_implementations, locate_subclasses
22
22
  from ._health import Health
23
23
  from ._log import LogSettings, get_logger
24
24
  from ._logfire import LogfireSettings
@@ -56,6 +56,7 @@ __all__ = [
56
56
  "console",
57
57
  "get_logger",
58
58
  "get_process_info",
59
+ "load_modules",
59
60
  "load_settings",
60
61
  "locate_implementations",
61
62
  "locate_subclasses",
@@ -0,0 +1,65 @@
1
+ """API router utilities for versioned FastAPI routers."""
2
+
3
+ from typing import ClassVar
4
+
5
+
6
+ class VersionedAPIRouter:
7
+ """APIRouter with version attribute.
8
+
9
+ - Use this class to create versioned routers for your FastAPI application
10
+ that are automatically registered into the FastAPI app.
11
+ - The version attribute is used to identify the version of the API
12
+ that the router corresponds to.
13
+ - See constants.por versions defined for this system.
14
+ """
15
+
16
+ # Class variable to track all created instances
17
+ _instances: ClassVar[list["VersionedAPIRouter"]] = []
18
+
19
+ @classmethod
20
+ def get_instances(cls) -> list["VersionedAPIRouter"]:
21
+ """Get all created router instances.
22
+
23
+ Returns:
24
+ A list of all router instances created.
25
+ """
26
+ return cls._instances.copy()
27
+
28
+ def __new__(cls, version: str, *args, **kwargs) -> "VersionedAPIRouter": # type: ignore[no-untyped-def]
29
+ """Create a new instance with lazy-loaded dependencies.
30
+
31
+ Args:
32
+ version: The API version this router belongs to.
33
+ *args: Arguments to pass to the FastAPI APIRouter.
34
+ **kwargs: Keyword arguments to pass to the FastAPI APIRouter.
35
+
36
+ Returns:
37
+ An instance of VersionedAPIRouter with lazy-loaded dependencies.
38
+ """
39
+ from fastapi import APIRouter # Import only when creating an instance # noqa: PLC0415
40
+
41
+ # Define the actual implementation class with the imports available
42
+ class VersionedAPIRouterImpl(APIRouter):
43
+ """Implementation of VersionedAPIRouter with lazy-loaded dependencies."""
44
+
45
+ version: str
46
+
47
+ def __init__(self, version: str, *args, **kwargs) -> None: # type: ignore[no-untyped-def]
48
+ """Initialize the router.
49
+
50
+ Args:
51
+ version: The API version this router belongs to.
52
+ *args: Arguments to pass to the FastAPI APIRouter.
53
+ **kwargs: Keyword arguments to pass to the FastAPI APIRouter.
54
+ """
55
+ super().__init__(*args, **kwargs)
56
+ self.version = version
57
+
58
+ # Create an instance
59
+ instance = VersionedAPIRouterImpl(version, *args, **kwargs)
60
+
61
+ # Add to registry of instances
62
+ cls._instances.append(instance) # type: ignore
63
+
64
+ # Return the instance but tell mypy it's a VersionedAPIRouter
65
+ return instance # type: ignore[return-value]
@@ -11,6 +11,12 @@ _implementation_cache: dict[Any, list[Any]] = {}
11
11
  _subclass_cache: dict[Any, list[Any]] = {}
12
12
 
13
13
 
14
+ def load_modules() -> None:
15
+ package = importlib.import_module(__project_name__)
16
+ for _, name, _ in pkgutil.iter_modules(package.__path__):
17
+ importlib.import_module(f"{__project_name__}.{name}")
18
+
19
+
14
20
  def locate_implementations(_class: type[Any]) -> list[Any]:
15
21
  """
16
22
  Dynamically discover all instances of some class.
@@ -0,0 +1,212 @@
1
+ import platform
2
+ from abc import ABC, abstractmethod
3
+ from pathlib import Path
4
+ from types import EllipsisType
5
+
6
+ from ._constants import __is_running_in_container__, __project_name__
7
+ from ._di import locate_subclasses
8
+ from ._log import get_logger
9
+
10
+ logger = get_logger(__name__)
11
+
12
+
13
+ class BasePageBuilder(ABC):
14
+ """Base class for all page builders."""
15
+
16
+ @staticmethod
17
+ @abstractmethod
18
+ def register_pages() -> None:
19
+ """Register pages."""
20
+
21
+
22
+ def gui_register_pages() -> None:
23
+ """Register pages.
24
+
25
+ This function is called by the GUI to register all pages.
26
+ """
27
+ page_builders = locate_subclasses(BasePageBuilder)
28
+ for page_builder in page_builders:
29
+ page_builder.register_pages()
30
+
31
+
32
+ def gui_run( # noqa: PLR0913, PLR0917
33
+ native: bool = True,
34
+ show: bool = False,
35
+ host: str | None = None,
36
+ port: int | None = None,
37
+ title: str = __project_name__,
38
+ icon: str = "",
39
+ watch: bool = False,
40
+ with_api: bool = False,
41
+ ) -> None:
42
+ """Start the GUI.
43
+
44
+ Args:
45
+ native: Whether to run the GUI in native mode.
46
+ show: Whether to show the GUI.
47
+ host: Host to run the GUI on.
48
+ port: Port to run the GUI on.
49
+ title: Title of the GUI.
50
+ icon: Icon for the GUI.
51
+ watch: Whether to watch for changes and reload the GUI.
52
+ with_api: Whether to mount the API.
53
+
54
+ Raises:
55
+ ValueError: If with_notebook is True but notebook_path is None,
56
+ or trying to run native within container.
57
+ """
58
+ from nicegui import app, ui # noqa: PLC0415
59
+ from nicegui import native as native_app # noqa: PLC0415
60
+
61
+ if __is_running_in_container__ and native:
62
+ message = "Native GUI cannot be run in a container. Please run with uvx or in browser."
63
+ raise ValueError(message)
64
+ if with_api:
65
+ from ..api import api # noqa: PLC0415, TID252
66
+
67
+ app.mount("/api", api)
68
+
69
+ gui_register_pages()
70
+ ui.run(
71
+ title=title,
72
+ favicon=icon,
73
+ native=native,
74
+ reload=watch,
75
+ dark=False,
76
+ host=host,
77
+ port=port or native_app.find_open_port(),
78
+ frameless=False,
79
+ show_welcome_message=True,
80
+ show=show,
81
+ )
82
+
83
+
84
+ class GUILocalFilePicker:
85
+ """Local File Picker dialog class that lazy-loads NiceGUI dependencies."""
86
+
87
+ def __new__( # noqa: C901
88
+ cls,
89
+ directory: str,
90
+ *,
91
+ upper_limit: str | EllipsisType | None = ...,
92
+ multiple: bool = False,
93
+ show_hidden_files: bool = False,
94
+ ) -> "GUILocalFilePicker":
95
+ """Create a new instance with lazy-loaded dependencies.
96
+
97
+ Args:
98
+ directory: The directory to start in.
99
+ upper_limit: The directory to stop at. None for no limit, default is same as starting directory.
100
+ multiple: Whether to allow multiple files to be selected.
101
+ show_hidden_files: Whether to show hidden files.
102
+
103
+ Returns:
104
+ An instance of the dialog with lazy-loaded dependencies.
105
+ """
106
+ from nicegui import events, ui # noqa: PLC0415
107
+ # Lazy import ui only when actually creating an instance
108
+
109
+ # Define the actual implementation class with the imports available
110
+ class GUILocalFilePickerImpl(ui.dialog):
111
+ def __init__(
112
+ self,
113
+ directory: str,
114
+ *,
115
+ upper_limit: str | EllipsisType | None = ...,
116
+ multiple: bool = False,
117
+ show_hidden_files: bool = False,
118
+ ) -> None:
119
+ """Local File Picker.
120
+
121
+ A simple file picker that allows selecting files from the local filesystem where NiceGUI is running.
122
+
123
+ Args:
124
+ directory: The directory to start in.
125
+ upper_limit: The directory to stop at. None for no limit, default is same as starting directory.
126
+ multiple: Whether to allow multiple files to be selected.
127
+ show_hidden_files: Whether to show hidden files.
128
+ """
129
+ super().__init__()
130
+
131
+ self.path = Path(directory).expanduser()
132
+ if upper_limit is None:
133
+ self.upper_limit = None
134
+ elif upper_limit is ...:
135
+ self.upper_limit = Path(directory).expanduser()
136
+ else:
137
+ self.upper_limit = Path(upper_limit).expanduser()
138
+ self.show_hidden_files = show_hidden_files
139
+
140
+ with self, ui.card():
141
+ self.add_drives_toggle()
142
+ self.grid = (
143
+ ui.aggrid(
144
+ {
145
+ "columnDefs": [{"field": "name", "headerName": "File"}],
146
+ "rowSelection": "multiple" if multiple else "single",
147
+ },
148
+ html_columns=[0],
149
+ )
150
+ .classes("w-96")
151
+ .on("cellDoubleClicked", self.handle_double_click)
152
+ )
153
+ with ui.row().classes("w-full justify-end"):
154
+ ui.button("Cancel", on_click=self.close).props("outline").mark("BUTTON_CANCEL")
155
+ ui.button("Ok", on_click=self._handle_ok).mark("BUTTON_OK")
156
+ self.update_grid()
157
+
158
+ def add_drives_toggle(self) -> None:
159
+ if platform.system() == "Windows":
160
+ import win32api # noqa: PLC0415
161
+
162
+ drives = win32api.GetLogicalDriveStrings().split("\000")[:-1]
163
+ self.drives_toggle = ui.toggle(drives, value=drives[0], on_change=self.update_drive)
164
+
165
+ def update_drive(self) -> None:
166
+ self.path = Path(self.drives_toggle.value).expanduser()
167
+ self.update_grid()
168
+
169
+ def update_grid(self) -> None:
170
+ paths = list(self.path.glob("*"))
171
+ if not self.show_hidden_files:
172
+ paths = [p for p in paths if not p.name.startswith(".")]
173
+ paths.sort(key=lambda p: p.name.lower())
174
+ paths.sort(key=lambda p: not p.is_dir())
175
+
176
+ self.grid.options["rowData"] = [
177
+ {
178
+ "name": f"📁 <strong>{p.name}</strong>" if p.is_dir() else p.name,
179
+ "path": str(p),
180
+ }
181
+ for p in paths
182
+ ]
183
+ if (self.upper_limit is None and self.path != self.path.parent) or (
184
+ self.upper_limit is not None and self.path != self.upper_limit
185
+ ):
186
+ self.grid.options["rowData"].insert(
187
+ 0,
188
+ {
189
+ "name": "📁 <strong>..</strong>",
190
+ "path": str(self.path.parent),
191
+ },
192
+ )
193
+ self.grid.update()
194
+
195
+ def handle_double_click(self, e: events.GenericEventArguments) -> None:
196
+ self.path = Path(e.args["data"]["path"])
197
+ if self.path.is_dir():
198
+ self.update_grid()
199
+ else:
200
+ self.submit([str(self.path)])
201
+
202
+ async def _handle_ok(self) -> None:
203
+ rows = await self.grid.get_selected_rows()
204
+ self.submit([r["path"] for r in rows])
205
+
206
+ # Create and return an instance but tell mypy it's a GUILocalFilePicker
207
+ return GUILocalFilePickerImpl( # type: ignore[return-value]
208
+ directory=directory,
209
+ upper_limit=upper_limit,
210
+ multiple=multiple,
211
+ show_hidden_files=show_hidden_files,
212
+ )
@@ -1,9 +1,7 @@
1
- """System service."""
1
+ """Notebook server utilities."""
2
2
 
3
3
  from collections.abc import Callable
4
-
5
- import marimo
6
- from fastapi import APIRouter, FastAPI
4
+ from typing import Any
7
5
 
8
6
  from ..constants import NOTEBOOK_APP, NOTEBOOK_FOLDER # noqa: TID252
9
7
  from ._health import Health
@@ -12,7 +10,7 @@ from ._log import get_logger
12
10
  logger = get_logger(__name__)
13
11
 
14
12
 
15
- def register_health_endpoint(router: APIRouter) -> Callable[..., Health]:
13
+ def register_health_endpoint(router: Any) -> Callable[..., Health]: # noqa: ANN401
16
14
  """Register health endpoint to the given router.
17
15
 
18
16
  Args:
@@ -21,6 +19,7 @@ def register_health_endpoint(router: APIRouter) -> Callable[..., Health]:
21
19
  Returns:
22
20
  Callable[..., Health]: The health endpoint function.
23
21
  """
22
+ # We accept 'Any' instead of APIRouter to avoid importing fastapi at module level
24
23
 
25
24
  @router.get("/healthz")
26
25
  def health_endpoint() -> Health:
@@ -31,10 +30,12 @@ def register_health_endpoint(router: APIRouter) -> Callable[..., Health]:
31
30
  """
32
31
  return Health(status=Health.Code.UP)
33
32
 
34
- return health_endpoint
33
+ # Explicitly type the return value to satisfy mypy
34
+ result: Callable[..., Health] = health_endpoint
35
+ return result
35
36
 
36
37
 
37
- def create_marimo_app() -> FastAPI:
38
+ def create_marimo_app() -> Any: # noqa: ANN401
38
39
  """Create a FastAPI app with marimo notebook server.
39
40
 
40
41
  Returns:
@@ -43,6 +44,10 @@ def create_marimo_app() -> FastAPI:
43
44
  Raises:
44
45
  ValueError: If the notebook directory does not exist.
45
46
  """
47
+ # Import dependencies only when function is called
48
+ import marimo # noqa: PLC0415
49
+ from fastapi import APIRouter, FastAPI # noqa: PLC0415
50
+
46
51
  server = marimo.create_asgi_app(include_code=True)
47
52
  if not NOTEBOOK_FOLDER.is_dir():
48
53
  logger.critical(
@@ -1,18 +0,0 @@
1
- from fastapi import APIRouter
2
-
3
-
4
- class VersionedAPIRouter(APIRouter):
5
- """APIRouter with version attribute.
6
-
7
- - Use this class to create versioned routers for your FastAPI application
8
- that are automatically registered into the FastAPI app.
9
- - The version attribute is used to identify the version of the API
10
- that the router corresponds to.
11
- - See constants.por versions defined for this system.
12
- """
13
-
14
- version: str
15
-
16
- def __init__(self, version: str, *args, **kwargs) -> None: # type: ignore[no-untyped-def]
17
- super().__init__(*args, **kwargs)
18
- self.version = version
@@ -1,178 +0,0 @@
1
- import platform
2
- from abc import ABC, abstractmethod
3
- from pathlib import Path
4
- from types import EllipsisType
5
-
6
- from nicegui import app, events, ui
7
- from nicegui import native as native_app
8
-
9
- from ._constants import __is_running_in_container__, __project_name__
10
- from ._di import locate_subclasses
11
- from ._log import get_logger
12
-
13
- logger = get_logger(__name__)
14
-
15
-
16
- class BasePageBuilder(ABC):
17
- """Base class for all page builders."""
18
-
19
- @staticmethod
20
- @abstractmethod
21
- def register_pages() -> None:
22
- """Register pages."""
23
-
24
-
25
- def gui_register_pages() -> None:
26
- """Register pages.
27
-
28
- This function is called by the GUI to register all pages.
29
- """
30
- page_builders = locate_subclasses(BasePageBuilder)
31
- for page_builder in page_builders:
32
- page_builder.register_pages()
33
-
34
-
35
- def gui_run( # noqa: PLR0913, PLR0917
36
- native: bool = True,
37
- show: bool = False,
38
- host: str | None = None,
39
- port: int | None = None,
40
- title: str = __project_name__,
41
- icon: str = "",
42
- watch: bool = False,
43
- with_api: bool = False,
44
- ) -> None:
45
- """Start the GUI.
46
-
47
- Args:
48
- native: Whether to run the GUI in native mode.
49
- show: Whether to show the GUI.
50
- host: Host to run the GUI on.
51
- port: Port to run the GUI on.
52
- title: Title of the GUI.
53
- icon: Icon for the GUI.
54
- watch: Whether to watch for changes and reload the GUI.
55
- with_api: Whether to mount the API.
56
-
57
- Raises:
58
- ValueError: If with_notebook is True but notebook_path is None,
59
- or trying to run native within container.
60
- """
61
- if __is_running_in_container__ and native:
62
- message = "Native GUI cannot be run in a container. Please run with uvx or in browser."
63
- raise ValueError(message)
64
- if with_api:
65
- from ..api import api # noqa: PLC0415, TID252
66
-
67
- app.mount("/api", api)
68
-
69
- gui_register_pages()
70
- ui.run(
71
- title=title,
72
- favicon=icon,
73
- native=native,
74
- reload=watch,
75
- dark=False,
76
- host=host,
77
- port=port or native_app.find_open_port(),
78
- frameless=False,
79
- show_welcome_message=True,
80
- show=show,
81
- )
82
-
83
-
84
- class GUILocalFilePicker(ui.dialog):
85
- def __init__(
86
- self,
87
- directory: str,
88
- *,
89
- upper_limit: str | EllipsisType | None = ...,
90
- multiple: bool = False,
91
- show_hidden_files: bool = False,
92
- ) -> None:
93
- """Local File Picker.
94
-
95
- A simple file picker that allows selecting files from the local filesystem where NiceGUI is running.
96
-
97
- Args:
98
- directory: The directory to start in.
99
- upper_limit: The directory to stop at. None for no limit, default is same as starting directory.
100
- multiple: Whether to allow multiple files to be selected.
101
- show_hidden_files: Whether to show hidden files.
102
- """
103
- super().__init__()
104
-
105
- self.path = Path(directory).expanduser()
106
- if upper_limit is None:
107
- self.upper_limit = None
108
- elif upper_limit is ...:
109
- self.upper_limit = Path(directory).expanduser()
110
- else:
111
- self.upper_limit = Path(upper_limit).expanduser()
112
- self.show_hidden_files = show_hidden_files
113
-
114
- with self, ui.card():
115
- self.add_drives_toggle()
116
- self.grid = (
117
- ui.aggrid(
118
- {
119
- "columnDefs": [{"field": "name", "headerName": "File"}],
120
- "rowSelection": "multiple" if multiple else "single",
121
- },
122
- html_columns=[0],
123
- )
124
- .classes("w-96")
125
- .on("cellDoubleClicked", self.handle_double_click)
126
- )
127
- with ui.row().classes("w-full justify-end"):
128
- ui.button("Cancel", on_click=self.close).props("outline").mark("BUTTON_CANCEL")
129
- ui.button("Ok", on_click=self._handle_ok).mark("BUTTON_OK")
130
- self.update_grid()
131
-
132
- def add_drives_toggle(self) -> None:
133
- if platform.system() == "Windows":
134
- import win32api # noqa: PLC0415
135
-
136
- drives = win32api.GetLogicalDriveStrings().split("\000")[:-1]
137
- self.drives_toggle = ui.toggle(drives, value=drives[0], on_change=self.update_drive)
138
-
139
- def update_drive(self) -> None:
140
- self.path = Path(self.drives_toggle.value).expanduser()
141
- self.update_grid()
142
-
143
- def update_grid(self) -> None:
144
- paths = list(self.path.glob("*"))
145
- if not self.show_hidden_files:
146
- paths = [p for p in paths if not p.name.startswith(".")]
147
- paths.sort(key=lambda p: p.name.lower())
148
- paths.sort(key=lambda p: not p.is_dir())
149
-
150
- self.grid.options["rowData"] = [
151
- {
152
- "name": f"📁 <strong>{p.name}</strong>" if p.is_dir() else p.name,
153
- "path": str(p),
154
- }
155
- for p in paths
156
- ]
157
- if (self.upper_limit is None and self.path != self.path.parent) or (
158
- self.upper_limit is not None and self.path != self.upper_limit
159
- ):
160
- self.grid.options["rowData"].insert(
161
- 0,
162
- {
163
- "name": "📁 <strong>..</strong>",
164
- "path": str(self.path.parent),
165
- },
166
- )
167
- self.grid.update()
168
-
169
- def handle_double_click(self, e: events.GenericEventArguments) -> None:
170
- self.path = Path(e.args["data"]["path"])
171
- if self.path.is_dir():
172
- self.update_grid()
173
- else:
174
- self.submit([str(self.path)])
175
-
176
- async def _handle_ok(self) -> None:
177
- rows = await self.grid.get_selected_rows()
178
- self.submit([r["path"] for r in rows])