memoryframes 0.5.0__tar.gz → 0.6.1__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.
- {memoryframes-0.5.0 → memoryframes-0.6.1}/PKG-INFO +3 -1
- {memoryframes-0.5.0 → memoryframes-0.6.1}/README.md +2 -0
- {memoryframes-0.5.0 → memoryframes-0.6.1}/pyproject.toml +2 -2
- memoryframes-0.6.1/src/MemoryFrames/AppState.py +94 -0
- {memoryframes-0.5.0 → memoryframes-0.6.1}/src/MemoryFrames/PluginInfra/Plugin.py +2 -2
- {memoryframes-0.5.0 → memoryframes-0.6.1}/src/MemoryFrames/PluginInfra/TextualUserExperienceInfo.py +2 -2
- memoryframes-0.6.1/src/MemoryFrames/TextualUserExperience.py +150 -0
- memoryframes-0.6.1/src/MemoryFrames/TextualUserExperience.tcss +33 -0
- {memoryframes-0.5.0 → memoryframes-0.6.1}/src/MemoryFrames/__main__.py +1 -1
- memoryframes-0.5.0/src/MemoryFrames/TextualUserExperience.py +0 -11
- {memoryframes-0.5.0 → memoryframes-0.6.1}/src/MemoryFrames/PluginInfra/Note.py +0 -0
- {memoryframes-0.5.0 → memoryframes-0.6.1}/src/MemoryFrames/PluginInfra/NoteSource.py +0 -0
- {memoryframes-0.5.0 → memoryframes-0.6.1}/src/MemoryFrames/PluginInfra/README.md +0 -0
- {memoryframes-0.5.0/src/MemoryFrames → memoryframes-0.6.1/src/MemoryFrames/PluginInfra}/Settings.py +0 -0
- {memoryframes-0.5.0 → memoryframes-0.6.1}/src/MemoryFrames/PluginInfra/UserExperienceInfo.py +0 -0
- {memoryframes-0.5.0 → memoryframes-0.6.1}/src/MemoryFrames/PluginInfra/__init__.py +0 -0
- {memoryframes-0.5.0 → memoryframes-0.6.1}/src/MemoryFrames/__init__.py +0 -0
- {memoryframes-0.5.0 → memoryframes-0.6.1}/src/MemoryFrames/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: memoryframes
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.1
|
|
4
4
|
Summary: Add your description here
|
|
5
5
|
Author: David Brownell
|
|
6
6
|
Author-email: David Brownell <github@DavidBrownell.com>
|
|
@@ -33,6 +33,8 @@ Description-Content-Type: text/markdown
|
|
|
33
33
|
|
|
34
34
|
**Development:**
|
|
35
35
|
[](https://github.com/astral-sh/uv)
|
|
36
|
+
[](https://github.com/astral-sh/ruff)
|
|
37
|
+
[](https://docs.pytest.org/)
|
|
36
38
|
[](https://github.com/davidbrownell/MemoryFrames/actions/workflows/CICD.yml)
|
|
37
39
|
[](https://github.com/davidbrownell/MemoryFrames/actions)
|
|
38
40
|
[](https://github.com/davidbrownell/MemoryFrames/commits/main/)
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
|
|
9
9
|
**Development:**
|
|
10
10
|
[](https://github.com/astral-sh/uv)
|
|
11
|
+
[](https://github.com/astral-sh/ruff)
|
|
12
|
+
[](https://docs.pytest.org/)
|
|
11
13
|
[](https://github.com/davidbrownell/MemoryFrames/actions/workflows/CICD.yml)
|
|
12
14
|
[](https://github.com/davidbrownell/MemoryFrames/actions)
|
|
13
15
|
[](https://github.com/davidbrownell/MemoryFrames/commits/main/)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "MemoryFrames"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.6.1"
|
|
4
4
|
# ^^^^^
|
|
5
5
|
# Wheel names will be generated according to this value. Do not manually modify this value; instead
|
|
6
6
|
# update it according to committed changes by running this command from the root of the repository:
|
|
@@ -59,7 +59,7 @@ dev = [
|
|
|
59
59
|
]
|
|
60
60
|
|
|
61
61
|
[tool.pytest.ini_options]
|
|
62
|
-
addopts = "--verbose -vv --capture=no --cov=MemoryFrames --cov-report html --cov-report term --cov-report xml:coverage.xml --cov-fail-under=
|
|
62
|
+
addopts = "--verbose -vv --capture=no --cov=MemoryFrames --cov-report html --cov-report term --cov-report xml:coverage.xml --cov-fail-under=75.0"
|
|
63
63
|
python_files = [
|
|
64
64
|
"**/*Test.py",
|
|
65
65
|
]
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from enum import auto, Enum
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
import pluggy
|
|
7
|
+
|
|
8
|
+
from MemoryFrames import APP_NAME
|
|
9
|
+
from MemoryFrames.PluginInfra import Plugin as PluginModule
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from MemoryFrames.PluginInfra.Plugin import Plugin
|
|
13
|
+
from MemoryFrames.PluginInfra.Settings import Settings
|
|
14
|
+
from MemoryFrames.PluginInfra.UserExperienceInfo import UserExperienceInfo
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# ----------------------------------------------------------------------
|
|
18
|
+
class AppStateObserver(ABC):
|
|
19
|
+
"""Observer for AppState changes."""
|
|
20
|
+
|
|
21
|
+
# ----------------------------------------------------------------------
|
|
22
|
+
class EventType(Enum):
|
|
23
|
+
"""Value signaling progress during the creation of an AppState instance."""
|
|
24
|
+
|
|
25
|
+
LoadingPlugins = auto()
|
|
26
|
+
|
|
27
|
+
# ----------------------------------------------------------------------
|
|
28
|
+
@abstractmethod
|
|
29
|
+
def OnEvent(self, event_type: EventType) -> None:
|
|
30
|
+
"""Invoke when a processing event begins."""
|
|
31
|
+
raise NotImplementedError() # pragma: no cover
|
|
32
|
+
|
|
33
|
+
# ----------------------------------------------------------------------
|
|
34
|
+
@abstractmethod
|
|
35
|
+
def OnException(self, exception: Exception) -> None:
|
|
36
|
+
"""Invoke when an exception occurs during processing."""
|
|
37
|
+
raise NotImplementedError() # pragma: no cover
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# ----------------------------------------------------------------------
|
|
41
|
+
@dataclass(frozen=True)
|
|
42
|
+
class AppState:
|
|
43
|
+
"""MemoryFrames application state."""
|
|
44
|
+
|
|
45
|
+
# ----------------------------------------------------------------------
|
|
46
|
+
plugins: list[Plugin]
|
|
47
|
+
|
|
48
|
+
# ----------------------------------------------------------------------
|
|
49
|
+
@classmethod
|
|
50
|
+
def Create(
|
|
51
|
+
cls,
|
|
52
|
+
settings: Settings,
|
|
53
|
+
user_experience_info: UserExperienceInfo,
|
|
54
|
+
observer: AppStateObserver,
|
|
55
|
+
) -> AppState | None:
|
|
56
|
+
"""Create an AppState instance."""
|
|
57
|
+
|
|
58
|
+
# Load the plugins
|
|
59
|
+
observer.OnEvent(AppStateObserver.EventType.LoadingPlugins)
|
|
60
|
+
|
|
61
|
+
try:
|
|
62
|
+
plugins = LoadPlugins(settings, user_experience_info)
|
|
63
|
+
except Exception as ex:
|
|
64
|
+
observer.OnException(ex)
|
|
65
|
+
return None
|
|
66
|
+
|
|
67
|
+
# We want the highest priority plugins first, and then sort by name. We apply the negative so
|
|
68
|
+
# that higher priority values appear first while still maintaining ascending order for names.
|
|
69
|
+
plugins.sort(key=lambda plugin: (-(plugin.PLUGIN_PRIORITY or 0), plugin.NAME))
|
|
70
|
+
|
|
71
|
+
return cls(plugins)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
# ----------------------------------------------------------------------
|
|
75
|
+
def LoadPlugins(
|
|
76
|
+
settings: Settings,
|
|
77
|
+
user_experience_info: UserExperienceInfo,
|
|
78
|
+
) -> list[Plugin]:
|
|
79
|
+
"""Load the plugins.
|
|
80
|
+
|
|
81
|
+
This is implemented as a separate function to make it easier to monkey-patch during testing.
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
plugin_manager = pluggy.PluginManager(APP_NAME)
|
|
85
|
+
|
|
86
|
+
plugin_manager.add_hookspecs(PluginModule)
|
|
87
|
+
plugin_manager.load_setuptools_entrypoints(APP_NAME)
|
|
88
|
+
|
|
89
|
+
plugins: list[Plugin] = plugin_manager.hook.GetPlugin(
|
|
90
|
+
settings=settings,
|
|
91
|
+
user_experience_info=user_experience_info,
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
return plugins
|
|
@@ -12,6 +12,7 @@ if TYPE_CHECKING:
|
|
|
12
12
|
from pathlib import Path
|
|
13
13
|
|
|
14
14
|
from MemoryFrames.PluginInfra.NoteSource import NoteSource, NoteSourceObserver
|
|
15
|
+
from MemoryFrames.PluginInfra.Settings import Settings
|
|
15
16
|
from MemoryFrames.PluginInfra.UserExperienceInfo import UserExperienceInfo
|
|
16
17
|
|
|
17
18
|
|
|
@@ -81,8 +82,7 @@ class Plugin:
|
|
|
81
82
|
# ----------------------------------------------------------------------
|
|
82
83
|
@pluggy.HookspecMarker(APP_NAME)
|
|
83
84
|
def GetPlugin(
|
|
84
|
-
|
|
85
|
-
all_plugins_settings: dict[str, object],
|
|
85
|
+
settings: Settings,
|
|
86
86
|
user_experience_info: UserExperienceInfo,
|
|
87
87
|
) -> Plugin:
|
|
88
88
|
"""Return a Plugin instance."""
|
{memoryframes-0.5.0 → memoryframes-0.6.1}/src/MemoryFrames/PluginInfra/TextualUserExperienceInfo.py
RENAMED
|
@@ -4,7 +4,7 @@ from MemoryFrames.PluginInfra.UserExperienceInfo import UserExperienceInfo
|
|
|
4
4
|
|
|
5
5
|
if TYPE_CHECKING:
|
|
6
6
|
from textual.app import App
|
|
7
|
-
from textual.containers import
|
|
7
|
+
from textual.containers import VerticalScroll
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
# ----------------------------------------------------------------------
|
|
@@ -15,7 +15,7 @@ class TextualUserExperienceInfo(UserExperienceInfo):
|
|
|
15
15
|
def __init__(
|
|
16
16
|
self,
|
|
17
17
|
app: App,
|
|
18
|
-
hierarchy_container:
|
|
18
|
+
hierarchy_container: VerticalScroll,
|
|
19
19
|
) -> None:
|
|
20
20
|
super().__init__()
|
|
21
21
|
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import traceback
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
from dbrownell_Common.Types import override
|
|
7
|
+
from textual.app import App, ComposeResult
|
|
8
|
+
from textual.containers import Horizontal, VerticalScroll
|
|
9
|
+
from textual.screen import ModalScreen
|
|
10
|
+
from textual.widgets import Footer, Header, Label, LoadingIndicator
|
|
11
|
+
|
|
12
|
+
from MemoryFrames import APP_NAME, __version__
|
|
13
|
+
from MemoryFrames.AppState import AppState, AppStateObserver as AppStateObserverBase
|
|
14
|
+
from MemoryFrames.PluginInfra.TextualUserExperienceInfo import TextualUserExperienceInfo
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from MemoryFrames.PluginInfra.Settings import Settings
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# ----------------------------------------------------------------------
|
|
21
|
+
def Execute(settings: Settings) -> None:
|
|
22
|
+
"""Execute the Textual user experience."""
|
|
23
|
+
|
|
24
|
+
_App(settings).run()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# ----------------------------------------------------------------------
|
|
28
|
+
# ----------------------------------------------------------------------
|
|
29
|
+
# ----------------------------------------------------------------------
|
|
30
|
+
class _App(App):
|
|
31
|
+
CSS_PATH = Path(__file__).with_suffix(".tcss")
|
|
32
|
+
|
|
33
|
+
# ----------------------------------------------------------------------
|
|
34
|
+
def __init__(self, settings: Settings) -> None:
|
|
35
|
+
super().__init__()
|
|
36
|
+
|
|
37
|
+
self._settings = settings
|
|
38
|
+
|
|
39
|
+
self._user_experience = TextualUserExperienceInfo(
|
|
40
|
+
self.app,
|
|
41
|
+
VerticalScroll(id="hierarchies"),
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
# The app state is initialized in `on_mount`
|
|
45
|
+
self._app_state: AppState | None = None
|
|
46
|
+
|
|
47
|
+
self.title = "Memory Frames"
|
|
48
|
+
assert self.title.replace(" ", "") == APP_NAME, (self.title, APP_NAME)
|
|
49
|
+
|
|
50
|
+
# ----------------------------------------------------------------------
|
|
51
|
+
def compose(self) -> ComposeResult:
|
|
52
|
+
yield Header()
|
|
53
|
+
|
|
54
|
+
with Horizontal(id="viewport"):
|
|
55
|
+
yield self._user_experience.hierarchy_container
|
|
56
|
+
|
|
57
|
+
with Horizontal(id="footer"):
|
|
58
|
+
yield Footer()
|
|
59
|
+
yield Label(__version__)
|
|
60
|
+
|
|
61
|
+
# ----------------------------------------------------------------------
|
|
62
|
+
def on_mount(self) -> None:
|
|
63
|
+
# ----------------------------------------------------------------------
|
|
64
|
+
def OnLoaded(app_state: AppState | None) -> None:
|
|
65
|
+
if app_state is None:
|
|
66
|
+
return
|
|
67
|
+
|
|
68
|
+
assert self._app_state is None
|
|
69
|
+
self._app_state = app_state
|
|
70
|
+
|
|
71
|
+
# ----------------------------------------------------------------------
|
|
72
|
+
|
|
73
|
+
self.push_screen(
|
|
74
|
+
_LoadingModal(self._settings, self._user_experience),
|
|
75
|
+
OnLoaded,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
# ----------------------------------------------------------------------
|
|
80
|
+
class _LoadingModal(ModalScreen[AppState]):
|
|
81
|
+
CSS_PATH = Path(__file__).with_suffix(".tcss")
|
|
82
|
+
|
|
83
|
+
# ----------------------------------------------------------------------
|
|
84
|
+
def __init__(
|
|
85
|
+
self,
|
|
86
|
+
settings: Settings,
|
|
87
|
+
user_experience_info: TextualUserExperienceInfo,
|
|
88
|
+
) -> None:
|
|
89
|
+
super().__init__()
|
|
90
|
+
|
|
91
|
+
self._settings = settings
|
|
92
|
+
self._user_experience_info = user_experience_info
|
|
93
|
+
|
|
94
|
+
self._status_label = Label("Loading...")
|
|
95
|
+
|
|
96
|
+
# ----------------------------------------------------------------------
|
|
97
|
+
def compose(self) -> ComposeResult:
|
|
98
|
+
yield LoadingIndicator()
|
|
99
|
+
yield self._status_label
|
|
100
|
+
|
|
101
|
+
# ----------------------------------------------------------------------
|
|
102
|
+
def on_mount(self) -> None:
|
|
103
|
+
loading_module = self
|
|
104
|
+
|
|
105
|
+
# ----------------------------------------------------------------------
|
|
106
|
+
def Execute() -> None:
|
|
107
|
+
current_event: AppStateObserverBase.EventType | None = None
|
|
108
|
+
|
|
109
|
+
# ----------------------------------------------------------------------
|
|
110
|
+
class AppStateObserver(AppStateObserverBase):
|
|
111
|
+
# ----------------------------------------------------------------------
|
|
112
|
+
@override
|
|
113
|
+
def OnEvent(self, event_type: AppStateObserver.EventType) -> None:
|
|
114
|
+
nonlocal current_event
|
|
115
|
+
current_event = event_type
|
|
116
|
+
|
|
117
|
+
if event_type == AppStateObserver.EventType.LoadingPlugins:
|
|
118
|
+
msg = "Loading plugins..."
|
|
119
|
+
elif event_type == AppStateObserver.EventType.StartingThreads:
|
|
120
|
+
msg = "Starting threads..."
|
|
121
|
+
else:
|
|
122
|
+
assert False, event_type # noqa: B011, PT015
|
|
123
|
+
|
|
124
|
+
loading_module._status_label.content = msg # noqa: SLF001
|
|
125
|
+
|
|
126
|
+
# ----------------------------------------------------------------------
|
|
127
|
+
@override
|
|
128
|
+
def OnException(self, exception: Exception) -> None:
|
|
129
|
+
if loading_module._settings.debug: # noqa: SLF001
|
|
130
|
+
msg = "".join(traceback.format_exception(exception))
|
|
131
|
+
else:
|
|
132
|
+
msg = str(exception)
|
|
133
|
+
|
|
134
|
+
if current_event == AppStateObserver.EventType.LoadingPlugins:
|
|
135
|
+
loading_module.app.panic(msg)
|
|
136
|
+
return
|
|
137
|
+
|
|
138
|
+
# Display in a toast
|
|
139
|
+
loading_module.notify(msg, severity="error", timeout=10)
|
|
140
|
+
|
|
141
|
+
# ----------------------------------------------------------------------
|
|
142
|
+
|
|
143
|
+
app_state = AppState.Create(self._settings, self._user_experience_info, AppStateObserver())
|
|
144
|
+
|
|
145
|
+
# Return the results
|
|
146
|
+
self.app.call_from_thread(lambda: self.dismiss(app_state))
|
|
147
|
+
|
|
148
|
+
# ----------------------------------------------------------------------
|
|
149
|
+
|
|
150
|
+
self.run_worker(Execute, thread=True)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
$version_length: 15;
|
|
2
|
+
|
|
3
|
+
#footer {
|
|
4
|
+
height: 1;
|
|
5
|
+
|
|
6
|
+
Footer {
|
|
7
|
+
padding-right: $version_length;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
Label {
|
|
11
|
+
background: $footer-background;
|
|
12
|
+
dock: right;
|
|
13
|
+
text-align: center;
|
|
14
|
+
width: $version_length;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
_LoadingModal {
|
|
19
|
+
align: center middle;
|
|
20
|
+
background: $panel;
|
|
21
|
+
height: 2;
|
|
22
|
+
padding: 1;
|
|
23
|
+
|
|
24
|
+
LoadingIndicator {
|
|
25
|
+
height: 1;
|
|
26
|
+
margin-bottom: 1;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
Label {
|
|
30
|
+
text-align: center;
|
|
31
|
+
width: 100%;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -8,8 +8,8 @@ import typer
|
|
|
8
8
|
|
|
9
9
|
from typer.core import TyperGroup
|
|
10
10
|
|
|
11
|
-
from MemoryFrames.Settings import Settings
|
|
12
11
|
from MemoryFrames import TextualUserExperience
|
|
12
|
+
from MemoryFrames.PluginInfra.Settings import Settings
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
# ----------------------------------------------------------------------
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
from typing import TYPE_CHECKING
|
|
2
|
-
|
|
3
|
-
if TYPE_CHECKING:
|
|
4
|
-
from MemoryFrames.Settings import Settings
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
# ----------------------------------------------------------------------
|
|
8
|
-
def Execute(settings: Settings) -> None:
|
|
9
|
-
"""Execute the Textual user experience."""
|
|
10
|
-
|
|
11
|
-
print("TextualUserExperience", settings) # noqa: T201
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{memoryframes-0.5.0/src/MemoryFrames → memoryframes-0.6.1/src/MemoryFrames/PluginInfra}/Settings.py
RENAMED
|
File without changes
|
{memoryframes-0.5.0 → memoryframes-0.6.1}/src/MemoryFrames/PluginInfra/UserExperienceInfo.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|