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.
Files changed (114) hide show
  1. refast/__init__.py +32 -0
  2. refast/app.py +330 -0
  3. refast/components/__init__.py +433 -0
  4. refast/components/base.py +264 -0
  5. refast/components/registry.py +139 -0
  6. refast/components/shadcn/__init__.py +421 -0
  7. refast/components/shadcn/button.py +151 -0
  8. refast/components/shadcn/card.py +213 -0
  9. refast/components/shadcn/charts/__init__.py +100 -0
  10. refast/components/shadcn/charts/area.py +212 -0
  11. refast/components/shadcn/charts/bar.py +221 -0
  12. refast/components/shadcn/charts/base.py +279 -0
  13. refast/components/shadcn/charts/composed.py +102 -0
  14. refast/components/shadcn/charts/funnel.py +155 -0
  15. refast/components/shadcn/charts/line.py +192 -0
  16. refast/components/shadcn/charts/pie.py +230 -0
  17. refast/components/shadcn/charts/radar.py +197 -0
  18. refast/components/shadcn/charts/radial.py +123 -0
  19. refast/components/shadcn/charts/sankey.py +125 -0
  20. refast/components/shadcn/charts/scatter.py +251 -0
  21. refast/components/shadcn/charts/treemap.py +113 -0
  22. refast/components/shadcn/charts/utils.py +868 -0
  23. refast/components/shadcn/controls.py +828 -0
  24. refast/components/shadcn/data_display.py +700 -0
  25. refast/components/shadcn/feedback.py +288 -0
  26. refast/components/shadcn/form.py +129 -0
  27. refast/components/shadcn/icon.py +311 -0
  28. refast/components/shadcn/input.py +585 -0
  29. refast/components/shadcn/layout.py +226 -0
  30. refast/components/shadcn/navigation.py +2349 -0
  31. refast/components/shadcn/overlay.py +1777 -0
  32. refast/components/shadcn/typography.py +212 -0
  33. refast/components/shadcn/utility.py +899 -0
  34. refast/components/slot.py +57 -0
  35. refast/context.py +1087 -0
  36. refast/events/__init__.py +19 -0
  37. refast/events/broadcast.py +247 -0
  38. refast/events/manager.py +267 -0
  39. refast/events/stream.py +322 -0
  40. refast/events/types.py +140 -0
  41. refast/extensions/__init__.py +9 -0
  42. refast/extensions/base.py +184 -0
  43. refast/router.py +421 -0
  44. refast/security/__init__.py +76 -0
  45. refast/security/csp.py +254 -0
  46. refast/security/csrf.py +305 -0
  47. refast/security/middleware.py +222 -0
  48. refast/security/rate_limit.py +312 -0
  49. refast/security/sanitizer.py +351 -0
  50. refast/session/__init__.py +23 -0
  51. refast/session/middleware.py +177 -0
  52. refast/session/session.py +231 -0
  53. refast/session/stores/__init__.py +13 -0
  54. refast/session/stores/base.py +111 -0
  55. refast/session/stores/memory.py +184 -0
  56. refast/session/stores/redis.py +168 -0
  57. refast/state.py +78 -0
  58. refast/static/App.d.ts +22 -0
  59. refast/static/__init__.py +1 -0
  60. refast/static/components/ComponentRenderer.d.ts +13 -0
  61. refast/static/components/ToastManager.d.ts +49 -0
  62. refast/static/components/__tests__/components.test.d.ts +1 -0
  63. refast/static/components/__tests__/registry.test.d.ts +1 -0
  64. refast/static/components/base.d.ts +32 -0
  65. refast/static/components/charts/area-chart.d.ts +2 -0
  66. refast/static/components/charts/bar-chart.d.ts +2 -0
  67. refast/static/components/charts/chart.d.ts +48 -0
  68. refast/static/components/charts/composed-chart.d.ts +1 -0
  69. refast/static/components/charts/funnel-chart.d.ts +4 -0
  70. refast/static/components/charts/line-chart.d.ts +2 -0
  71. refast/static/components/charts/pie-chart.d.ts +6 -0
  72. refast/static/components/charts/radar-chart.d.ts +10 -0
  73. refast/static/components/charts/radial-chart.d.ts +4 -0
  74. refast/static/components/charts/sankey.d.ts +3 -0
  75. refast/static/components/charts/scatter-chart.d.ts +5 -0
  76. refast/static/components/charts/treemap.d.ts +3 -0
  77. refast/static/components/charts/utils.d.ts +13 -0
  78. refast/static/components/registry.d.ts +19 -0
  79. refast/static/components/shadcn/ConnectionStatus.d.ts +36 -0
  80. refast/static/components/shadcn/button.d.ts +35 -0
  81. refast/static/components/shadcn/calendar.d.ts +7 -0
  82. refast/static/components/shadcn/card.d.ts +63 -0
  83. refast/static/components/shadcn/controls.d.ts +189 -0
  84. refast/static/components/shadcn/data_display.d.ts +186 -0
  85. refast/static/components/shadcn/feedback.d.ts +86 -0
  86. refast/static/components/shadcn/icon.d.ts +25 -0
  87. refast/static/components/shadcn/input.d.ts +190 -0
  88. refast/static/components/shadcn/layout.d.ts +61 -0
  89. refast/static/components/shadcn/navigation.d.ts +493 -0
  90. refast/static/components/shadcn/overlay.d.ts +306 -0
  91. refast/static/components/shadcn/slot.d.ts +14 -0
  92. refast/static/components/shadcn/types.d.ts +24 -0
  93. refast/static/components/shadcn/typography.d.ts +113 -0
  94. refast/static/components/shadcn/utility.d.ts +127 -0
  95. refast/static/events/EventManager.d.ts +32 -0
  96. refast/static/events/WebSocketClient.d.ts +40 -0
  97. refast/static/index.d.ts +61 -0
  98. refast/static/state/PersistentStateManager.d.ts +78 -0
  99. refast/static/state/PropStore.d.ts +67 -0
  100. refast/static/state/StateManager.d.ts +22 -0
  101. refast/static/state/__tests__/StateManager.test.d.ts +1 -0
  102. refast/static/test/setup.d.ts +1 -0
  103. refast/static/types.d.ts +180 -0
  104. refast/static/utils/__tests__/index.test.d.ts +1 -0
  105. refast/static/utils/index.d.ts +26 -0
  106. refast/store.py +423 -0
  107. refast/theme/__init__.py +27 -0
  108. refast/theme/presets.py +252 -0
  109. refast/theme/theme.py +191 -0
  110. refast/utils/__init__.py +15 -0
  111. refast/utils/case.py +244 -0
  112. refast-0.0.1.dist-info/METADATA +79 -0
  113. refast-0.0.1.dist-info/RECORD +114 -0
  114. 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)