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.
- {oe_python_template-0.14.3 → oe_python_template-0.14.4}/.gitignore +2 -1
- {oe_python_template-0.14.3 → oe_python_template-0.14.4}/PKG-INFO +1 -1
- {oe_python_template-0.14.3 → oe_python_template-0.14.4}/pyproject.toml +3 -2
- {oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/api.py +7 -6
- {oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/hello/_api.py +3 -3
- {oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/hello/_gui.py +5 -3
- {oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/system/_api.py +1 -1
- {oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/system/_gui.py +2 -2
- {oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/utils/__init__.py +2 -1
- oe_python_template-0.14.4/src/oe_python_template/utils/_api.py +65 -0
- {oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/utils/_di.py +6 -0
- oe_python_template-0.14.4/src/oe_python_template/utils/_gui.py +212 -0
- {oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/utils/_notebook.py +12 -7
- oe_python_template-0.14.3/src/oe_python_template/utils/_api.py +0 -18
- oe_python_template-0.14.3/src/oe_python_template/utils/_gui.py +0 -178
- {oe_python_template-0.14.3 → oe_python_template-0.14.4}/LICENSE +0 -0
- {oe_python_template-0.14.3 → oe_python_template-0.14.4}/README.md +0 -0
- {oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/__init__.py +0 -0
- {oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/cli.py +0 -0
- {oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/constants.py +0 -0
- {oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/hello/__init__.py +0 -0
- {oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/hello/_cli.py +0 -0
- {oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/hello/_constants.py +0 -0
- {oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/hello/_models.py +0 -0
- {oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/hello/_service.py +0 -0
- {oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/hello/_settings.py +0 -0
- {oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/system/__init__.py +0 -0
- {oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/system/_cli.py +0 -0
- {oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/system/_service.py +0 -0
- {oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/system/_settings.py +0 -0
- {oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/utils/.vendored/bottle.py +0 -0
- {oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/utils/_cli.py +0 -0
- {oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/utils/_console.py +0 -0
- {oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/utils/_constants.py +0 -0
- {oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/utils/_health.py +0 -0
- {oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/utils/_log.py +0 -0
- {oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/utils/_logfire.py +0 -0
- {oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/utils/_process.py +0 -0
- {oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/utils/_sentry.py +0 -0
- {oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/utils/_service.py +0 -0
- {oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/utils/_settings.py +0 -0
- {oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/utils/boot.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: oe-python-template
|
|
3
|
-
Version: 0.14.
|
|
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
|
+
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.
|
|
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
|
-
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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(
|
|
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:
|
{oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/hello/_api.py
RENAMED
|
@@ -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]:
|
{oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/hello/_gui.py
RENAMED
|
@@ -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
|
-
|
|
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."""
|
{oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/system/_api.py
RENAMED
|
@@ -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])
|
{oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/system/_gui.py
RENAMED
|
@@ -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."""
|
{oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/utils/__init__.py
RENAMED
|
@@ -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
|
+
)
|
{oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/utils/_notebook.py
RENAMED
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
"""
|
|
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:
|
|
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
|
|
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() ->
|
|
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])
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/hello/__init__.py
RENAMED
|
File without changes
|
{oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/hello/_cli.py
RENAMED
|
File without changes
|
{oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/hello/_constants.py
RENAMED
|
File without changes
|
{oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/hello/_models.py
RENAMED
|
File without changes
|
{oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/hello/_service.py
RENAMED
|
File without changes
|
{oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/hello/_settings.py
RENAMED
|
File without changes
|
{oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/system/__init__.py
RENAMED
|
File without changes
|
{oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/system/_cli.py
RENAMED
|
File without changes
|
{oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/system/_service.py
RENAMED
|
File without changes
|
{oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/system/_settings.py
RENAMED
|
File without changes
|
|
File without changes
|
{oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/utils/_cli.py
RENAMED
|
File without changes
|
{oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/utils/_console.py
RENAMED
|
File without changes
|
{oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/utils/_constants.py
RENAMED
|
File without changes
|
{oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/utils/_health.py
RENAMED
|
File without changes
|
{oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/utils/_log.py
RENAMED
|
File without changes
|
{oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/utils/_logfire.py
RENAMED
|
File without changes
|
{oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/utils/_process.py
RENAMED
|
File without changes
|
{oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/utils/_sentry.py
RENAMED
|
File without changes
|
{oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/utils/_service.py
RENAMED
|
File without changes
|
{oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/utils/_settings.py
RENAMED
|
File without changes
|
{oe_python_template-0.14.3 → oe_python_template-0.14.4}/src/oe_python_template/utils/boot.py
RENAMED
|
File without changes
|