refast 0.0.1__py3-none-any.whl
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.
- refast/__init__.py +32 -0
- refast/app.py +330 -0
- refast/components/__init__.py +433 -0
- refast/components/base.py +264 -0
- refast/components/registry.py +139 -0
- refast/components/shadcn/__init__.py +421 -0
- refast/components/shadcn/button.py +151 -0
- refast/components/shadcn/card.py +213 -0
- refast/components/shadcn/charts/__init__.py +100 -0
- refast/components/shadcn/charts/area.py +212 -0
- refast/components/shadcn/charts/bar.py +221 -0
- refast/components/shadcn/charts/base.py +279 -0
- refast/components/shadcn/charts/composed.py +102 -0
- refast/components/shadcn/charts/funnel.py +155 -0
- refast/components/shadcn/charts/line.py +192 -0
- refast/components/shadcn/charts/pie.py +230 -0
- refast/components/shadcn/charts/radar.py +197 -0
- refast/components/shadcn/charts/radial.py +123 -0
- refast/components/shadcn/charts/sankey.py +125 -0
- refast/components/shadcn/charts/scatter.py +251 -0
- refast/components/shadcn/charts/treemap.py +113 -0
- refast/components/shadcn/charts/utils.py +868 -0
- refast/components/shadcn/controls.py +828 -0
- refast/components/shadcn/data_display.py +700 -0
- refast/components/shadcn/feedback.py +288 -0
- refast/components/shadcn/form.py +129 -0
- refast/components/shadcn/icon.py +311 -0
- refast/components/shadcn/input.py +585 -0
- refast/components/shadcn/layout.py +226 -0
- refast/components/shadcn/navigation.py +2349 -0
- refast/components/shadcn/overlay.py +1777 -0
- refast/components/shadcn/typography.py +212 -0
- refast/components/shadcn/utility.py +899 -0
- refast/components/slot.py +57 -0
- refast/context.py +1087 -0
- refast/events/__init__.py +19 -0
- refast/events/broadcast.py +247 -0
- refast/events/manager.py +267 -0
- refast/events/stream.py +322 -0
- refast/events/types.py +140 -0
- refast/extensions/__init__.py +9 -0
- refast/extensions/base.py +184 -0
- refast/router.py +421 -0
- refast/security/__init__.py +76 -0
- refast/security/csp.py +254 -0
- refast/security/csrf.py +305 -0
- refast/security/middleware.py +222 -0
- refast/security/rate_limit.py +312 -0
- refast/security/sanitizer.py +351 -0
- refast/session/__init__.py +23 -0
- refast/session/middleware.py +177 -0
- refast/session/session.py +231 -0
- refast/session/stores/__init__.py +13 -0
- refast/session/stores/base.py +111 -0
- refast/session/stores/memory.py +184 -0
- refast/session/stores/redis.py +168 -0
- refast/state.py +78 -0
- refast/static/App.d.ts +22 -0
- refast/static/__init__.py +1 -0
- refast/static/components/ComponentRenderer.d.ts +13 -0
- refast/static/components/ToastManager.d.ts +49 -0
- refast/static/components/__tests__/components.test.d.ts +1 -0
- refast/static/components/__tests__/registry.test.d.ts +1 -0
- refast/static/components/base.d.ts +32 -0
- refast/static/components/charts/area-chart.d.ts +2 -0
- refast/static/components/charts/bar-chart.d.ts +2 -0
- refast/static/components/charts/chart.d.ts +48 -0
- refast/static/components/charts/composed-chart.d.ts +1 -0
- refast/static/components/charts/funnel-chart.d.ts +4 -0
- refast/static/components/charts/line-chart.d.ts +2 -0
- refast/static/components/charts/pie-chart.d.ts +6 -0
- refast/static/components/charts/radar-chart.d.ts +10 -0
- refast/static/components/charts/radial-chart.d.ts +4 -0
- refast/static/components/charts/sankey.d.ts +3 -0
- refast/static/components/charts/scatter-chart.d.ts +5 -0
- refast/static/components/charts/treemap.d.ts +3 -0
- refast/static/components/charts/utils.d.ts +13 -0
- refast/static/components/registry.d.ts +19 -0
- refast/static/components/shadcn/ConnectionStatus.d.ts +36 -0
- refast/static/components/shadcn/button.d.ts +35 -0
- refast/static/components/shadcn/calendar.d.ts +7 -0
- refast/static/components/shadcn/card.d.ts +63 -0
- refast/static/components/shadcn/controls.d.ts +189 -0
- refast/static/components/shadcn/data_display.d.ts +186 -0
- refast/static/components/shadcn/feedback.d.ts +86 -0
- refast/static/components/shadcn/icon.d.ts +25 -0
- refast/static/components/shadcn/input.d.ts +190 -0
- refast/static/components/shadcn/layout.d.ts +61 -0
- refast/static/components/shadcn/navigation.d.ts +493 -0
- refast/static/components/shadcn/overlay.d.ts +306 -0
- refast/static/components/shadcn/slot.d.ts +14 -0
- refast/static/components/shadcn/types.d.ts +24 -0
- refast/static/components/shadcn/typography.d.ts +113 -0
- refast/static/components/shadcn/utility.d.ts +127 -0
- refast/static/events/EventManager.d.ts +32 -0
- refast/static/events/WebSocketClient.d.ts +40 -0
- refast/static/index.d.ts +61 -0
- refast/static/state/PersistentStateManager.d.ts +78 -0
- refast/static/state/PropStore.d.ts +67 -0
- refast/static/state/StateManager.d.ts +22 -0
- refast/static/state/__tests__/StateManager.test.d.ts +1 -0
- refast/static/test/setup.d.ts +1 -0
- refast/static/types.d.ts +180 -0
- refast/static/utils/__tests__/index.test.d.ts +1 -0
- refast/static/utils/index.d.ts +26 -0
- refast/store.py +423 -0
- refast/theme/__init__.py +27 -0
- refast/theme/presets.py +252 -0
- refast/theme/theme.py +191 -0
- refast/utils/__init__.py +15 -0
- refast/utils/case.py +244 -0
- refast-0.0.1.dist-info/METADATA +79 -0
- refast-0.0.1.dist-info/RECORD +114 -0
- refast-0.0.1.dist-info/WHEEL +4 -0
refast/__init__.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Refast - Python + React UI Framework
|
|
3
|
+
|
|
4
|
+
A framework for building reactive web applications with Python-first development.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from refast.app import RefastApp
|
|
8
|
+
from refast.context import BoundJsCallback, Callback, Context, JsAction, JsCallback
|
|
9
|
+
from refast.extensions import Extension
|
|
10
|
+
from refast.state import State
|
|
11
|
+
from refast.store import BrowserStore, JSONEncoder, LocalStore, SessionStore, Store
|
|
12
|
+
from refast.theme import Theme, ThemeColors, ThemeMode
|
|
13
|
+
|
|
14
|
+
__version__ = "0.1.0"
|
|
15
|
+
__all__ = [
|
|
16
|
+
"RefastApp",
|
|
17
|
+
"Context",
|
|
18
|
+
"Callback",
|
|
19
|
+
"JsCallback",
|
|
20
|
+
"JsAction",
|
|
21
|
+
"BoundJsCallback",
|
|
22
|
+
"State",
|
|
23
|
+
"Store",
|
|
24
|
+
"LocalStore",
|
|
25
|
+
"SessionStore",
|
|
26
|
+
"BrowserStore",
|
|
27
|
+
"JSONEncoder",
|
|
28
|
+
"Extension",
|
|
29
|
+
"Theme",
|
|
30
|
+
"ThemeColors",
|
|
31
|
+
"ThemeMode",
|
|
32
|
+
]
|
refast/app.py
ADDED
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
"""Main RefastApp class."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from collections.abc import Callable
|
|
5
|
+
from typing import TYPE_CHECKING, Any, TypeVar
|
|
6
|
+
|
|
7
|
+
from fastapi import APIRouter
|
|
8
|
+
|
|
9
|
+
from refast.router import RefastRouter
|
|
10
|
+
from refast.theme.theme import Theme
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from refast.context import Context
|
|
14
|
+
from refast.extensions import Extension
|
|
15
|
+
|
|
16
|
+
PageFunc = TypeVar("PageFunc", bound=Callable[..., Any])
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class RefastApp:
|
|
22
|
+
"""
|
|
23
|
+
Main Refast application class.
|
|
24
|
+
|
|
25
|
+
Example:
|
|
26
|
+
```python
|
|
27
|
+
from refast import RefastApp
|
|
28
|
+
from refast.theme import rose_theme
|
|
29
|
+
|
|
30
|
+
ui = RefastApp(
|
|
31
|
+
title="My App",
|
|
32
|
+
theme=rose_theme,
|
|
33
|
+
favicon="/static/favicon.ico",
|
|
34
|
+
custom_css=["https://fonts.googleapis.com/css2?family=Inter&display=swap"],
|
|
35
|
+
custom_js=["console.log('loaded');"],
|
|
36
|
+
head_tags=['<meta name="description" content="My Refast App">'],
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
@ui.page("/")
|
|
40
|
+
def home(ctx: Context):
|
|
41
|
+
return Container(Text("Hello World"))
|
|
42
|
+
|
|
43
|
+
# Mount to FastAPI
|
|
44
|
+
app.include_router(ui.router, prefix="/ui")
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
title: Application title
|
|
49
|
+
theme: Theme configuration — a ``Theme`` instance, or ``None`` for defaults
|
|
50
|
+
secret_key: Secret key for session encryption
|
|
51
|
+
debug: Enable debug mode
|
|
52
|
+
favicon: URL to a favicon (e.g. ``"/static/favicon.ico"``)
|
|
53
|
+
custom_css: Additional CSS — inline snippets or URLs. URLs (starting
|
|
54
|
+
with ``http`` or ``/``) are injected as ``<link>`` tags; anything
|
|
55
|
+
else is wrapped in an inline ``<style>`` block.
|
|
56
|
+
custom_js: Additional JavaScript — inline snippets or URLs. URLs are
|
|
57
|
+
injected as ``<script src>`` tags; anything else is wrapped in
|
|
58
|
+
an inline ``<script>`` block. Placed at the end of ``<body>``.
|
|
59
|
+
head_tags: Raw HTML strings injected verbatim into ``<head>``
|
|
60
|
+
(e.g. ``<meta>``, ``<link>``, ``<style>``).
|
|
61
|
+
extensions: List of Extension instances to register
|
|
62
|
+
auto_discover_extensions: Whether to auto-discover extensions via entry points
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
def __init__(
|
|
66
|
+
self,
|
|
67
|
+
title: str = "Refast App",
|
|
68
|
+
theme: Theme | None = None,
|
|
69
|
+
secret_key: str | None = None,
|
|
70
|
+
debug: bool = False,
|
|
71
|
+
favicon: str | None = None,
|
|
72
|
+
custom_css: str | list[str] | None = None,
|
|
73
|
+
custom_js: str | list[str] | None = None,
|
|
74
|
+
head_tags: list[str] | None = None,
|
|
75
|
+
extensions: list["Extension"] | None = None,
|
|
76
|
+
auto_discover_extensions: bool = True,
|
|
77
|
+
):
|
|
78
|
+
self.title = title
|
|
79
|
+
self.theme = theme
|
|
80
|
+
self.secret_key = secret_key
|
|
81
|
+
self.debug = debug
|
|
82
|
+
self.favicon = favicon
|
|
83
|
+
|
|
84
|
+
# Normalise custom_css / custom_js to lists
|
|
85
|
+
if custom_css is None:
|
|
86
|
+
self._custom_css: list[str] = []
|
|
87
|
+
elif isinstance(custom_css, str):
|
|
88
|
+
self._custom_css = [custom_css]
|
|
89
|
+
else:
|
|
90
|
+
self._custom_css = list(custom_css)
|
|
91
|
+
|
|
92
|
+
if custom_js is None:
|
|
93
|
+
self._custom_js: list[str] = []
|
|
94
|
+
elif isinstance(custom_js, str):
|
|
95
|
+
self._custom_js = [custom_js]
|
|
96
|
+
else:
|
|
97
|
+
self._custom_js = list(custom_js)
|
|
98
|
+
|
|
99
|
+
self._head_tags: list[str] = list(head_tags) if head_tags else []
|
|
100
|
+
|
|
101
|
+
self._pages: dict[str, Callable] = {}
|
|
102
|
+
self._callbacks: dict[str, Callable] = {}
|
|
103
|
+
self._event_handlers: dict[str, Callable] = {}
|
|
104
|
+
self._router: RefastRouter | None = None
|
|
105
|
+
self._extensions: dict[str, Extension] = {}
|
|
106
|
+
|
|
107
|
+
# Auto-discover extensions via entry points
|
|
108
|
+
if auto_discover_extensions:
|
|
109
|
+
self._discover_extensions()
|
|
110
|
+
|
|
111
|
+
# Register manually provided extensions
|
|
112
|
+
if extensions:
|
|
113
|
+
for ext in extensions:
|
|
114
|
+
self.register_extension(ext)
|
|
115
|
+
|
|
116
|
+
@property
|
|
117
|
+
def router(self) -> APIRouter:
|
|
118
|
+
"""Get the FastAPI router for mounting."""
|
|
119
|
+
if self._router is None:
|
|
120
|
+
self._router = RefastRouter(self)
|
|
121
|
+
return self._router.api_router
|
|
122
|
+
|
|
123
|
+
@property
|
|
124
|
+
def active_contexts(self) -> list["Context"]:
|
|
125
|
+
"""Get all active WebSocket contexts."""
|
|
126
|
+
if self._router is None:
|
|
127
|
+
return []
|
|
128
|
+
return self._router.active_contexts
|
|
129
|
+
|
|
130
|
+
@property
|
|
131
|
+
def pages(self) -> dict[str, Callable]:
|
|
132
|
+
"""Get registered pages."""
|
|
133
|
+
return self._pages.copy()
|
|
134
|
+
|
|
135
|
+
def page(self, path: str) -> Callable[[PageFunc], PageFunc]:
|
|
136
|
+
"""
|
|
137
|
+
Decorator to register a page.
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
path: URL path for the page
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
Decorator function
|
|
144
|
+
|
|
145
|
+
Example:
|
|
146
|
+
```python
|
|
147
|
+
@ui.page("/dashboard")
|
|
148
|
+
def dashboard(ctx: Context):
|
|
149
|
+
return Container(...)
|
|
150
|
+
```
|
|
151
|
+
"""
|
|
152
|
+
|
|
153
|
+
def decorator(func: PageFunc) -> PageFunc:
|
|
154
|
+
self._pages[path] = func
|
|
155
|
+
return func
|
|
156
|
+
|
|
157
|
+
return decorator
|
|
158
|
+
|
|
159
|
+
def on_event(self, event_type: str) -> Callable[[Callable], Callable]:
|
|
160
|
+
"""
|
|
161
|
+
Decorator to register an event handler.
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
event_type: The event type to handle (e.g., "user:click")
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
Decorator function
|
|
168
|
+
"""
|
|
169
|
+
|
|
170
|
+
def decorator(func: Callable) -> Callable:
|
|
171
|
+
self._event_handlers[event_type] = func
|
|
172
|
+
return func
|
|
173
|
+
|
|
174
|
+
return decorator
|
|
175
|
+
|
|
176
|
+
def register_callback(self, callback_id: str, func: Callable) -> None:
|
|
177
|
+
"""Register a callback function."""
|
|
178
|
+
self._callbacks[callback_id] = func
|
|
179
|
+
|
|
180
|
+
def get_callback(self, callback_id: str) -> Callable | None:
|
|
181
|
+
"""Get a registered callback by ID."""
|
|
182
|
+
return self._callbacks.get(callback_id)
|
|
183
|
+
|
|
184
|
+
# Extension methods
|
|
185
|
+
|
|
186
|
+
@property
|
|
187
|
+
def extensions(self) -> dict[str, "Extension"]:
|
|
188
|
+
"""Get registered extensions."""
|
|
189
|
+
return self._extensions.copy()
|
|
190
|
+
|
|
191
|
+
def register_extension(self, extension: "Extension") -> None:
|
|
192
|
+
"""
|
|
193
|
+
Register an extension with this app.
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
extension: The Extension instance to register.
|
|
197
|
+
|
|
198
|
+
Raises:
|
|
199
|
+
ValueError: If an extension with the same name is already registered.
|
|
200
|
+
|
|
201
|
+
Example:
|
|
202
|
+
```python
|
|
203
|
+
from refast_leaflet import LeafletExtension
|
|
204
|
+
|
|
205
|
+
ui = RefastApp()
|
|
206
|
+
ui.register_extension(LeafletExtension())
|
|
207
|
+
```
|
|
208
|
+
"""
|
|
209
|
+
from refast.extensions import Extension
|
|
210
|
+
|
|
211
|
+
if not isinstance(extension, Extension):
|
|
212
|
+
raise TypeError(f"Expected Extension instance, got {type(extension).__name__}")
|
|
213
|
+
|
|
214
|
+
if extension.name in self._extensions:
|
|
215
|
+
raise ValueError(f"Extension '{extension.name}' is already registered")
|
|
216
|
+
|
|
217
|
+
# Validate extension configuration
|
|
218
|
+
errors = extension.validate()
|
|
219
|
+
if errors and self.debug:
|
|
220
|
+
for error in errors:
|
|
221
|
+
logger.warning(f"Extension validation warning: {error}")
|
|
222
|
+
|
|
223
|
+
# Register the extension
|
|
224
|
+
self._extensions[extension.name] = extension
|
|
225
|
+
|
|
226
|
+
# Call the extension's on_register hook
|
|
227
|
+
extension.on_register(self)
|
|
228
|
+
|
|
229
|
+
logger.debug(f"Registered extension: {extension.name} v{extension.version}")
|
|
230
|
+
|
|
231
|
+
def _discover_extensions(self) -> None:
|
|
232
|
+
"""
|
|
233
|
+
Discover and register extensions via Python entry points.
|
|
234
|
+
|
|
235
|
+
Extensions can register themselves using the 'refast.extensions'
|
|
236
|
+
entry point group in their pyproject.toml:
|
|
237
|
+
|
|
238
|
+
[project.entry-points."refast.extensions"]
|
|
239
|
+
my_extension = "my_package:MyExtension"
|
|
240
|
+
"""
|
|
241
|
+
try:
|
|
242
|
+
from importlib.metadata import entry_points
|
|
243
|
+
except ImportError:
|
|
244
|
+
# Python < 3.10 fallback
|
|
245
|
+
from importlib_metadata import entry_points # type: ignore
|
|
246
|
+
|
|
247
|
+
try:
|
|
248
|
+
# Python 3.10+ style
|
|
249
|
+
eps = entry_points(group="refast.extensions")
|
|
250
|
+
except TypeError:
|
|
251
|
+
# Python 3.9 style
|
|
252
|
+
all_eps = entry_points()
|
|
253
|
+
eps = all_eps.get("refast.extensions", [])
|
|
254
|
+
|
|
255
|
+
for ep in eps:
|
|
256
|
+
try:
|
|
257
|
+
extension_class = ep.load()
|
|
258
|
+
extension = extension_class()
|
|
259
|
+
self.register_extension(extension)
|
|
260
|
+
logger.debug(f"Auto-discovered extension: {ep.name}")
|
|
261
|
+
except Exception as e:
|
|
262
|
+
logger.warning(f"Failed to load extension '{ep.name}': {e}")
|
|
263
|
+
|
|
264
|
+
def get_extension(self, name: str) -> "Extension | None":
|
|
265
|
+
"""
|
|
266
|
+
Get a registered extension by name.
|
|
267
|
+
|
|
268
|
+
Args:
|
|
269
|
+
name: The extension name.
|
|
270
|
+
|
|
271
|
+
Returns:
|
|
272
|
+
The Extension instance, or None if not found.
|
|
273
|
+
"""
|
|
274
|
+
return self._extensions.get(name)
|
|
275
|
+
|
|
276
|
+
# ------------------------------------------------------------------
|
|
277
|
+
# Theming & customisation helpers
|
|
278
|
+
# ------------------------------------------------------------------
|
|
279
|
+
|
|
280
|
+
def add_css(self, css: str) -> None:
|
|
281
|
+
"""
|
|
282
|
+
Add a CSS snippet or URL after construction.
|
|
283
|
+
|
|
284
|
+
URLs (starting with ``http`` or ``/``) become ``<link>`` tags;
|
|
285
|
+
everything else is wrapped in an inline ``<style>`` block.
|
|
286
|
+
|
|
287
|
+
Args:
|
|
288
|
+
css: A CSS URL or inline CSS string.
|
|
289
|
+
|
|
290
|
+
Example:
|
|
291
|
+
```python
|
|
292
|
+
ui.add_css("https://fonts.googleapis.com/css2?family=Inter")
|
|
293
|
+
ui.add_css("body { font-family: Inter, sans-serif; }")
|
|
294
|
+
```
|
|
295
|
+
"""
|
|
296
|
+
self._custom_css.append(css)
|
|
297
|
+
|
|
298
|
+
def add_js(self, js: str) -> None:
|
|
299
|
+
"""
|
|
300
|
+
Add a JavaScript snippet or URL after construction.
|
|
301
|
+
|
|
302
|
+
URLs (starting with ``http`` or ``/``) become ``<script src>`` tags;
|
|
303
|
+
everything else is wrapped in an inline ``<script>`` block.
|
|
304
|
+
Scripts are placed at the end of ``<body>``.
|
|
305
|
+
|
|
306
|
+
Args:
|
|
307
|
+
js: A JS URL or inline script string.
|
|
308
|
+
|
|
309
|
+
Example:
|
|
310
|
+
```python
|
|
311
|
+
ui.add_js("https://cdn.example.com/lib.js")
|
|
312
|
+
ui.add_js("console.log('app ready');")
|
|
313
|
+
```
|
|
314
|
+
"""
|
|
315
|
+
self._custom_js.append(js)
|
|
316
|
+
|
|
317
|
+
def add_head_tag(self, html: str) -> None:
|
|
318
|
+
"""
|
|
319
|
+
Add a raw HTML string to ``<head>``.
|
|
320
|
+
|
|
321
|
+
Args:
|
|
322
|
+
html: A raw HTML tag (``<meta>``, ``<link>``, etc.).
|
|
323
|
+
|
|
324
|
+
Example:
|
|
325
|
+
```python
|
|
326
|
+
ui.add_head_tag('<meta name="description" content="My app">')
|
|
327
|
+
ui.add_head_tag('<link rel="preconnect" href="https://fonts.gstatic.com">')
|
|
328
|
+
```
|
|
329
|
+
"""
|
|
330
|
+
self._head_tags.append(html)
|