facetkit 0.1.0__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.
@@ -0,0 +1,31 @@
1
+ # Python artifacts
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ .Python
7
+
8
+ # Packaging
9
+ build/
10
+ dist/
11
+ *.egg-info/
12
+ *.egg
13
+
14
+ # Virtual environments
15
+ .venv/
16
+ venv/
17
+ .env/
18
+ env/
19
+
20
+ # Test / type / lint caches
21
+ .pytest_cache/
22
+ .mypy_cache/
23
+ .ruff_cache/
24
+ .coverage
25
+ htmlcov/
26
+
27
+ # IDE
28
+ .vscode/
29
+ .idea/
30
+ *.swp
31
+ *.swo
@@ -0,0 +1,19 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.1.0] - 2026-06-18
9
+
10
+ ### Added
11
+
12
+ - `Container` with config, component lifecycle, and facet mounting
13
+ - Passive facets: `CliFacet`, `TuiFacet`, `GuiFacet`, `WebFacet`, `ServiceFacet`
14
+ - `Component` attach/detach protocol for composable plugins
15
+ - `Container.get()` introspection via glom paths
16
+ - Descriptor types for registry entries (`Command`, `RouteDescriptor`, etc.)
17
+ - CLI command descriptions derived from handler docstrings
18
+
19
+ [0.1.0]: https://github.com/Dev-DanielR/py_facetkit/releases/tag/v0.1.0
facetkit-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Daniel R. Vásquez Montes
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,214 @@
1
+ Metadata-Version: 2.4
2
+ Name: facetkit
3
+ Version: 0.1.0
4
+ Summary: A composable dependency container with passive UI and service facets.
5
+ Author-email: "Daniel R. Vásquez Montes" <dev.DanielR@gmail.composable>
6
+ License: MIT
7
+ License-File: LICENSE
8
+ Keywords: cli,container,dependency-injection,gui,plugin,registry,tui,web
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Topic :: Software Development :: Libraries
18
+ Classifier: Typing :: Typed
19
+ Requires-Python: >=3.10
20
+ Requires-Dist: glom>=23.0
21
+ Provides-Extra: dev
22
+ Requires-Dist: build>=1.0; extra == 'dev'
23
+ Requires-Dist: pytest>=8.0; extra == 'dev'
24
+ Requires-Dist: twine>=5.0; extra == 'dev'
25
+ Description-Content-Type: text/markdown
26
+
27
+ # facetkit
28
+
29
+ A composable Python container for application state and passive registries.
30
+ Provides basic functionality for CLI commands, TUI screens, GUI widgets, web routes and background services.
31
+
32
+ ## Design
33
+
34
+ - **Container** — shared config, component lifecycle, and a map of mounted facets
35
+ - **Facets** — framework-agnostic registries (commands, routes, widgets, tasks, etc)
36
+ - **Components** — plugins that register into facets on `attach` and clean up on `detach`
37
+
38
+ ```
39
+ Container
40
+ ├── config
41
+ ├── components
42
+ └── facets
43
+ ├── cli → commands
44
+ ├── tui → screens, keybindings
45
+ ├── gui → widgets, menus, toolbars, layouts
46
+ ├── web → routes, middleware, error handlers
47
+ └── service → tasks, providers
48
+ ```
49
+
50
+ ## Requirements
51
+
52
+ - Python 3.10+
53
+ - [glom](https://github.com/mahmoud/glom) (declared as a dependency)
54
+
55
+ ## Installation
56
+
57
+ ```bash
58
+ pip install facetkit
59
+ ```
60
+
61
+ For local development:
62
+
63
+ ```bash
64
+ git clone https://github.com/Dev-DanielR/py_facetkit.git
65
+ cd facetkit
66
+ pip install -e ".[dev]"
67
+ ```
68
+
69
+ ## Quick start
70
+
71
+ ```python
72
+ from facetkit import Container, CliFacet, WebFacet
73
+
74
+ app = Container({"app": {"name": "demo"}})
75
+
76
+ app.mount_facet("cli", CliFacet())
77
+ app.mount_facet("web", WebFacet())
78
+
79
+ def hello():
80
+ """Say hello."""
81
+ return "Hello!"
82
+
83
+ cli = app.facets["cli"]
84
+ cli.add_command("hello", hello)
85
+
86
+ web = app.facets["web"]
87
+ web.add_route("hello", "/hello", lambda: {"message": "Hello!"}, methods=["GET"])
88
+ ```
89
+
90
+ Your application reads the registries and dispatches however you like — argparse, FastAPI, Textual, Qt, etc.
91
+
92
+ ## Components
93
+
94
+ Components implement `attach(ctx)` and `detach(ctx)`. The container is passed as context:
95
+
96
+ ```python
97
+ class StatusComponent:
98
+ def attach(self, ctx):
99
+ ctx.facets["cli"].add_command("status", self.show_status)
100
+ ctx.facets["service"].add_provider("status", {"healthy": True})
101
+
102
+ def detach(self, ctx):
103
+ ctx.facets["cli"].remove_command("status")
104
+ ctx.facets["service"].remove_provider("status")
105
+
106
+ def show_status(self):
107
+ """Show application status."""
108
+ return "ok"
109
+
110
+ app.mount_facet("cli", CliFacet())
111
+ app.mount_facet("service", ServiceFacet())
112
+ app.add_component("status", StatusComponent())
113
+ ```
114
+
115
+ Replacing or removing a component calls `detach` on the old instance before the new one attaches.
116
+
117
+ ## Facets
118
+
119
+ | Facet | Registries | Purpose |
120
+ |-------|------------|---------|
121
+ | `CliFacet` | `commands` | Named CLI commands. Description is taken from the handler's docstring |
122
+ | `TuiFacet` | `screens`, `keybindings`, `current_screen` | Terminal UI descriptors |
123
+ | `GuiFacet` | `widgets`, `menus`, `toolbars`, `layouts` | Desktop UI descriptors |
124
+ | `WebFacet` | `routes`, `middleware`, `error_handlers` | HTTP/API descriptors |
125
+ | `ServiceFacet` | `tasks`, `providers` | Background work and shared providers |
126
+
127
+ Mount only what you need:
128
+
129
+ ```python
130
+ app.mount_facet("cli", CliFacet())
131
+ app.mount_facet("web", WebFacet())
132
+ # No TUI/GUI facets required
133
+ ```
134
+
135
+ Unmounting clears the facet's registries:
136
+
137
+ ```python
138
+ app.unmount_facet("cli")
139
+ ```
140
+
141
+ ### Registry behavior
142
+
143
+ - **Duplicate keys** — registering the same name again overwrites the previous entry (last wins).
144
+ - **Unmount** — `unmount_facet` clears that facet's registries. Attached components are not automatically detached; call `remove_component` first if you need a full teardown.
145
+ - **Facet names** — the mount name is arbitrary (`app.mount_facet("cli", CliFacet())`). The library does not enforce that the name matches the facet type.
146
+ - **Missing facets** — `app.facets["cli"]` raises `KeyError`; `app.get("facets.cli")` returns `None`.
147
+
148
+ ## Introspection with `get()`
149
+
150
+ `Container.get(path)` uses [glom](https://glom.readthedocs.io/) to read nested state:
151
+
152
+ ```python
153
+ app.get("") # the container itself
154
+ app.get("config.app.name") # config values
155
+ app.get("facets") # all mounted facets
156
+ app.get("facets.cli") # CliFacet instance
157
+ app.get("facets.cli.commands") # command registry
158
+ app.get("facets.web.routes.users") # a single route entry
159
+ ```
160
+
161
+ Missing paths return `default` (or `None`):
162
+
163
+ ```python
164
+ app.get("facets.missing") # None
165
+ app.get("facets.missing", default={}) # {}
166
+ ```
167
+
168
+ `get()` also returns `default` on any internal error while resolving a path, not only missing keys. That keeps introspection safe but can hide bugs — prefer direct attribute access when you want exceptions to surface.
169
+
170
+ Direct dict access also works when you know a facet is mounted:
171
+
172
+ ```python
173
+ app.facets["cli"].commands["hello"]
174
+ ```
175
+
176
+ ## Public API
177
+
178
+ ```python
179
+ from facetkit import (
180
+ Container,
181
+ Component, # protocol
182
+ Facet, # protocol
183
+ CliFacet,
184
+ TuiFacet,
185
+ GuiFacet,
186
+ WebFacet,
187
+ ServiceFacet,
188
+ Command,
189
+ RouteDescriptor,
190
+ # ... other descriptor types
191
+ )
192
+ ```
193
+
194
+ ## Versioning
195
+
196
+ This project is pre-1.0 (`0.1.0`). Public APIs may change between minor releases. See [CHANGELOG.md](CHANGELOG.md) for release notes.
197
+
198
+ ## Development
199
+
200
+ ```bash
201
+ pip install -e ".[dev]"
202
+ pytest
203
+ ```
204
+
205
+ Build a wheel locally:
206
+
207
+ ```bash
208
+ pip install build
209
+ python -m build
210
+ ```
211
+
212
+ ## License
213
+
214
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,188 @@
1
+ # facetkit
2
+
3
+ A composable Python container for application state and passive registries.
4
+ Provides basic functionality for CLI commands, TUI screens, GUI widgets, web routes and background services.
5
+
6
+ ## Design
7
+
8
+ - **Container** — shared config, component lifecycle, and a map of mounted facets
9
+ - **Facets** — framework-agnostic registries (commands, routes, widgets, tasks, etc)
10
+ - **Components** — plugins that register into facets on `attach` and clean up on `detach`
11
+
12
+ ```
13
+ Container
14
+ ├── config
15
+ ├── components
16
+ └── facets
17
+ ├── cli → commands
18
+ ├── tui → screens, keybindings
19
+ ├── gui → widgets, menus, toolbars, layouts
20
+ ├── web → routes, middleware, error handlers
21
+ └── service → tasks, providers
22
+ ```
23
+
24
+ ## Requirements
25
+
26
+ - Python 3.10+
27
+ - [glom](https://github.com/mahmoud/glom) (declared as a dependency)
28
+
29
+ ## Installation
30
+
31
+ ```bash
32
+ pip install facetkit
33
+ ```
34
+
35
+ For local development:
36
+
37
+ ```bash
38
+ git clone https://github.com/Dev-DanielR/py_facetkit.git
39
+ cd facetkit
40
+ pip install -e ".[dev]"
41
+ ```
42
+
43
+ ## Quick start
44
+
45
+ ```python
46
+ from facetkit import Container, CliFacet, WebFacet
47
+
48
+ app = Container({"app": {"name": "demo"}})
49
+
50
+ app.mount_facet("cli", CliFacet())
51
+ app.mount_facet("web", WebFacet())
52
+
53
+ def hello():
54
+ """Say hello."""
55
+ return "Hello!"
56
+
57
+ cli = app.facets["cli"]
58
+ cli.add_command("hello", hello)
59
+
60
+ web = app.facets["web"]
61
+ web.add_route("hello", "/hello", lambda: {"message": "Hello!"}, methods=["GET"])
62
+ ```
63
+
64
+ Your application reads the registries and dispatches however you like — argparse, FastAPI, Textual, Qt, etc.
65
+
66
+ ## Components
67
+
68
+ Components implement `attach(ctx)` and `detach(ctx)`. The container is passed as context:
69
+
70
+ ```python
71
+ class StatusComponent:
72
+ def attach(self, ctx):
73
+ ctx.facets["cli"].add_command("status", self.show_status)
74
+ ctx.facets["service"].add_provider("status", {"healthy": True})
75
+
76
+ def detach(self, ctx):
77
+ ctx.facets["cli"].remove_command("status")
78
+ ctx.facets["service"].remove_provider("status")
79
+
80
+ def show_status(self):
81
+ """Show application status."""
82
+ return "ok"
83
+
84
+ app.mount_facet("cli", CliFacet())
85
+ app.mount_facet("service", ServiceFacet())
86
+ app.add_component("status", StatusComponent())
87
+ ```
88
+
89
+ Replacing or removing a component calls `detach` on the old instance before the new one attaches.
90
+
91
+ ## Facets
92
+
93
+ | Facet | Registries | Purpose |
94
+ |-------|------------|---------|
95
+ | `CliFacet` | `commands` | Named CLI commands. Description is taken from the handler's docstring |
96
+ | `TuiFacet` | `screens`, `keybindings`, `current_screen` | Terminal UI descriptors |
97
+ | `GuiFacet` | `widgets`, `menus`, `toolbars`, `layouts` | Desktop UI descriptors |
98
+ | `WebFacet` | `routes`, `middleware`, `error_handlers` | HTTP/API descriptors |
99
+ | `ServiceFacet` | `tasks`, `providers` | Background work and shared providers |
100
+
101
+ Mount only what you need:
102
+
103
+ ```python
104
+ app.mount_facet("cli", CliFacet())
105
+ app.mount_facet("web", WebFacet())
106
+ # No TUI/GUI facets required
107
+ ```
108
+
109
+ Unmounting clears the facet's registries:
110
+
111
+ ```python
112
+ app.unmount_facet("cli")
113
+ ```
114
+
115
+ ### Registry behavior
116
+
117
+ - **Duplicate keys** — registering the same name again overwrites the previous entry (last wins).
118
+ - **Unmount** — `unmount_facet` clears that facet's registries. Attached components are not automatically detached; call `remove_component` first if you need a full teardown.
119
+ - **Facet names** — the mount name is arbitrary (`app.mount_facet("cli", CliFacet())`). The library does not enforce that the name matches the facet type.
120
+ - **Missing facets** — `app.facets["cli"]` raises `KeyError`; `app.get("facets.cli")` returns `None`.
121
+
122
+ ## Introspection with `get()`
123
+
124
+ `Container.get(path)` uses [glom](https://glom.readthedocs.io/) to read nested state:
125
+
126
+ ```python
127
+ app.get("") # the container itself
128
+ app.get("config.app.name") # config values
129
+ app.get("facets") # all mounted facets
130
+ app.get("facets.cli") # CliFacet instance
131
+ app.get("facets.cli.commands") # command registry
132
+ app.get("facets.web.routes.users") # a single route entry
133
+ ```
134
+
135
+ Missing paths return `default` (or `None`):
136
+
137
+ ```python
138
+ app.get("facets.missing") # None
139
+ app.get("facets.missing", default={}) # {}
140
+ ```
141
+
142
+ `get()` also returns `default` on any internal error while resolving a path, not only missing keys. That keeps introspection safe but can hide bugs — prefer direct attribute access when you want exceptions to surface.
143
+
144
+ Direct dict access also works when you know a facet is mounted:
145
+
146
+ ```python
147
+ app.facets["cli"].commands["hello"]
148
+ ```
149
+
150
+ ## Public API
151
+
152
+ ```python
153
+ from facetkit import (
154
+ Container,
155
+ Component, # protocol
156
+ Facet, # protocol
157
+ CliFacet,
158
+ TuiFacet,
159
+ GuiFacet,
160
+ WebFacet,
161
+ ServiceFacet,
162
+ Command,
163
+ RouteDescriptor,
164
+ # ... other descriptor types
165
+ )
166
+ ```
167
+
168
+ ## Versioning
169
+
170
+ This project is pre-1.0 (`0.1.0`). Public APIs may change between minor releases. See [CHANGELOG.md](CHANGELOG.md) for release notes.
171
+
172
+ ## Development
173
+
174
+ ```bash
175
+ pip install -e ".[dev]"
176
+ pytest
177
+ ```
178
+
179
+ Build a wheel locally:
180
+
181
+ ```bash
182
+ pip install build
183
+ python -m build
184
+ ```
185
+
186
+ ## License
187
+
188
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,41 @@
1
+ from facetkit.container import Container
2
+ from facetkit.types import (
3
+ Component,
4
+ Facet,
5
+ Command,
6
+ TaskDescriptor,
7
+ ScreenDescriptor,
8
+ KeybindingDescriptor,
9
+ WidgetDescriptor,
10
+ MenuDescriptor,
11
+ ToolbarDescriptor,
12
+ LayoutDescriptor,
13
+ RouteDescriptor,
14
+ MiddlewareDescriptor,
15
+ ErrorHandlerDescriptor,
16
+ )
17
+ from facetkit.facets import CliFacet, ServiceFacet, TuiFacet, GuiFacet, WebFacet
18
+
19
+ __all__ = [
20
+ "Container",
21
+ "Component",
22
+ "Facet",
23
+ "Command",
24
+ "TaskDescriptor",
25
+ "ScreenDescriptor",
26
+ "KeybindingDescriptor",
27
+ "WidgetDescriptor",
28
+ "MenuDescriptor",
29
+ "ToolbarDescriptor",
30
+ "LayoutDescriptor",
31
+ "RouteDescriptor",
32
+ "MiddlewareDescriptor",
33
+ "ErrorHandlerDescriptor",
34
+ "CliFacet",
35
+ "ServiceFacet",
36
+ "TuiFacet",
37
+ "GuiFacet",
38
+ "WebFacet",
39
+ ]
40
+
41
+ __version__ = "0.1.0"
@@ -0,0 +1,51 @@
1
+ #===============================================================================
2
+ # DEPENDENCIES
3
+
4
+ import glom
5
+
6
+ from typing import Dict, Any, Optional
7
+ from facetkit.types import Component, Facet
8
+
9
+ #===============================================================================
10
+ # DEFINITIONS
11
+
12
+ class Container:
13
+
14
+ def __init__(self, config: Dict[str, Any]):
15
+ self.config : Dict[str, Any] = config
16
+ self.facets : Dict[str, Facet] = {}
17
+ self.components : Dict[str, Component] = {}
18
+
19
+ # Public API ===============================================================
20
+
21
+ def get(self, path: str, default: Any = None) -> Any:
22
+ """Retrieve from container using glom."""
23
+
24
+ if not path: return self
25
+ try:
26
+ return glom.glom(self.__dict__, path)
27
+ except (glom.PathAccessError, glom.GlomError):
28
+ return default
29
+ except Exception:
30
+ return default
31
+
32
+ def mount_facet(self, name: str, facet: Facet) -> None:
33
+ if name in self.facets: self.unmount_facet(name)
34
+ self.facets[name] = facet
35
+
36
+ def unmount_facet(self, name: str) -> None:
37
+ facet = self.facets.pop(name, None)
38
+ if facet: facet.clear()
39
+
40
+ def add_component(self, name: str, comp: Component) -> None:
41
+ """Register a component and attach it to this container."""
42
+
43
+ if name in self.components: self.remove_component(name)
44
+ comp.attach(self)
45
+ self.components[name] = comp
46
+
47
+ def remove_component(self, name: str) -> None:
48
+ """Remove a component and detach it from this container."""
49
+
50
+ comp = self.components.pop(name, None)
51
+ if comp: comp.detach(self)
@@ -0,0 +1,7 @@
1
+ from facetkit.facets.cli import CliFacet
2
+ from facetkit.facets.gui import GuiFacet
3
+ from facetkit.facets.service import ServiceFacet
4
+ from facetkit.facets.tui import TuiFacet
5
+ from facetkit.facets.web import WebFacet
6
+
7
+ __all__ = ["CliFacet", "ServiceFacet", "TuiFacet", "GuiFacet", "WebFacet"]
@@ -0,0 +1,26 @@
1
+ #===============================================================================
2
+ # DEPENDENCIES
3
+
4
+ from textwrap import dedent
5
+
6
+ from typing import Any, Callable, Dict
7
+ from facetkit.types import Command, Facet
8
+
9
+ #===============================================================================
10
+ # DEFINITIONS
11
+
12
+ class CliFacet(Facet):
13
+
14
+ def __init__(self):
15
+ self.name = "cli"
16
+ self.commands : Dict[str, Command] = {}
17
+
18
+ def add_command(self, name: str, handler: Callable[..., Any]) -> None:
19
+ doc = handler.__doc__ or ""
20
+ self.commands[name] = Command(name, handler, dedent(doc).strip())
21
+
22
+ def remove_command(self, name: str) -> None:
23
+ self.commands.pop(name, None)
24
+
25
+ def clear(self) -> None:
26
+ self.commands.clear()
@@ -0,0 +1,53 @@
1
+ #===============================================================================
2
+ # DEPENDENCIES
3
+
4
+ from typing import Any, Callable, Dict, Optional
5
+ from facetkit.types import Facet, LayoutDescriptor, MenuDescriptor, ToolbarDescriptor, WidgetDescriptor
6
+
7
+ #===============================================================================
8
+ # DEFINITIONS
9
+
10
+ class GuiFacet(Facet):
11
+
12
+ def __init__(self):
13
+ self.name = "gui"
14
+ self.widgets : Dict[str, WidgetDescriptor] = {}
15
+ self.menus : Dict[str, MenuDescriptor] = {}
16
+ self.toolbars : Dict[str, ToolbarDescriptor] = {}
17
+ self.layouts : Dict[str, LayoutDescriptor] = {}
18
+
19
+ def add_widget(
20
+ self,
21
+ widget_id: str,
22
+ factory: Callable[..., Any],
23
+ parent: Optional[str] = None,
24
+ layout_hints: Optional[Dict[str, Any]] = None,
25
+ ) -> None:
26
+ self.widgets[widget_id] = WidgetDescriptor(widget_id, factory, parent, layout_hints or {})
27
+
28
+ def remove_widget(self, widget_id: str) -> None:
29
+ self.widgets.pop(widget_id, None)
30
+
31
+ def add_menu(self, menu_id: str, factory: Callable[..., Any], parent: Optional[str] = None) -> None:
32
+ self.menus[menu_id] = MenuDescriptor(menu_id, factory, parent)
33
+
34
+ def remove_menu(self, menu_id: str) -> None:
35
+ self.menus.pop(menu_id, None)
36
+
37
+ def add_toolbar(self, toolbar_id: str, factory: Callable[..., Any], parent: Optional[str] = None) -> None:
38
+ self.toolbars[toolbar_id] = ToolbarDescriptor(toolbar_id, factory, parent)
39
+
40
+ def remove_toolbar(self, toolbar_id: str) -> None:
41
+ self.toolbars.pop(toolbar_id, None)
42
+
43
+ def add_layout(self, layout_id: str, factory: Callable[..., Any], hints: Optional[Dict[str, Any]] = None) -> None:
44
+ self.layouts[layout_id] = LayoutDescriptor(layout_id, factory, hints or {})
45
+
46
+ def remove_layout(self, layout_id: str) -> None:
47
+ self.layouts.pop(layout_id, None)
48
+
49
+ def clear(self) -> None:
50
+ self.widgets.clear()
51
+ self.menus.clear()
52
+ self.toolbars.clear()
53
+ self.layouts.clear()
@@ -0,0 +1,31 @@
1
+ #===============================================================================
2
+ # DEPENDENCIES
3
+
4
+ from typing import Any, Callable, Dict, Optional
5
+ from facetkit.types import Facet, TaskDescriptor
6
+
7
+ #===============================================================================
8
+ # DEFINITIONS
9
+
10
+ class ServiceFacet(Facet):
11
+
12
+ def __init__(self):
13
+ self.name = "service"
14
+ self.tasks : Dict[str, TaskDescriptor] = {}
15
+ self.providers : Dict[str, Any] = {}
16
+
17
+ def add_task(self, name: str, factory: Callable[..., Any], interval: Optional[float] = None) -> None:
18
+ self.tasks[name] = TaskDescriptor(name, factory, interval)
19
+
20
+ def remove_task(self, name: str) -> None:
21
+ self.tasks.pop(name, None)
22
+
23
+ def add_provider(self, name: str, provider: Any) -> None:
24
+ self.providers[name] = provider
25
+
26
+ def remove_provider(self, name: str) -> None:
27
+ self.providers.pop(name, None)
28
+
29
+ def clear(self) -> None:
30
+ self.tasks.clear()
31
+ self.providers.clear()