violit 0.0.5__py3-none-any.whl → 0.0.6__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.
@@ -6,82 +6,98 @@ from ..context import rendering_ctx
6
6
 
7
7
 
8
8
  class TextWidgetsMixin:
9
- def write(self, *args, tag: Optional[str] = "div", unsafe_allow_html: bool = False, **props):
10
- """Display content with automatic type detection"""
11
- from ..state import State
12
- import re
13
- import json
14
- import html as html_lib
9
+ def write(self, *args, **kwargs):
10
+ """Magic write: displays arguments based on their type
15
11
 
16
- cid = self._get_next_cid("comp")
12
+ Supported types:
13
+ - Strings, Numbers, State: Rendered as Markdown text
14
+ - Pandas DataFrame/Series: Rendered as interactive table
15
+ - Dict/List: Rendered as JSON tree
16
+ - Matplotlib/Plotly Figures: Rendered as charts
17
+ - Exceptions: Rendered as error trace
18
+ """
19
+ from ..state import State, ComputedState
17
20
 
18
- def builder():
19
- def _has_markdown(text: str) -> bool:
20
- """Check if text contains markdown syntax"""
21
- markdown_patterns = [
22
- r'^#{1,6}\s', # Headers: # ## ###
23
- r'\*\*[^*]+\*\*', # Bold: **text**
24
- r'(?<!\*)\*[^*\n]+\*', # Italic: *text*
25
- r'`[^`]+`', # Code: `text`
26
- r'\[.+?\]\(.+?\)', # Links: [text](url)
27
- r'^[-*]\s', # Lists: - or *
28
- r'^\d+\.\s', # Numbered lists: 1. 2.
29
- ]
30
- for pattern in markdown_patterns:
31
- if re.search(pattern, text, re.MULTILINE):
32
- return True
33
- return False
21
+ # Buffer for text-like arguments
22
+ text_buffer = []
23
+
24
+ def flush_buffer():
25
+ if text_buffer:
26
+ self.markdown(*text_buffer)
27
+ text_buffer.clear()
28
+
29
+ for arg in args:
30
+ # Unwrap state for type checking ONLY
31
+ check_val = arg.value if isinstance(arg, (State, ComputedState)) else arg
34
32
 
35
- # Set rendering context once for entire builder
36
- token = rendering_ctx.set(cid)
37
- parts = []
33
+ # 1. Pandas DataFrame / Series
34
+ is_df = False
35
+ try:
36
+ import pandas as pd
37
+ if isinstance(check_val, (pd.DataFrame, pd.Series, pd.Index)):
38
+ is_df = True
39
+ except ImportError: pass
38
40
 
41
+ if is_df:
42
+ flush_buffer()
43
+ if hasattr(self, 'dataframe'):
44
+ self.dataframe(arg)
45
+ else:
46
+ self.markdown(str(arg))
47
+ continue
48
+
49
+ # 2. Matplotlib Figure
50
+ is_pyplot = False
39
51
  try:
40
- for arg in args:
41
- current_value = arg
42
-
43
- # State object: read value (registers dependency)
44
- if isinstance(arg, State):
45
- current_value = arg.value
46
-
47
- # Callable/Lambda: execute (registers dependency)
48
- elif callable(arg):
49
- current_value = arg()
50
-
51
- # DataFrame (pandas)
52
- try:
53
- import pandas as pd
54
- if isinstance(current_value, pd.DataFrame):
55
- parts.append(self._render_dataframe_html(current_value))
56
- continue
57
- except (ImportError, AttributeError):
58
- pass
59
-
60
- # Dict or List → JSON
61
- if isinstance(current_value, (dict, list, tuple)):
62
- json_str = json.dumps(current_value, indent=2, ensure_ascii=False)
63
- parts.append(f'<pre style="background:var(--sl-bg-card);padding:1rem;border-radius:0.5rem;border:1px solid var(--sl-border);overflow-x:auto;"><code style="color:var(--sl-text);font-family:monospace;">{html_lib.escape(json_str)}</code></pre>')
64
- continue
65
-
66
- # String with markdown → render as markdown
67
- text = str(current_value)
68
- if _has_markdown(text):
69
- parts.append(self._render_markdown(text))
70
- else:
71
- # Plain text
72
- parts.append(text)
52
+ import matplotlib.figure
53
+ if isinstance(check_val, matplotlib.figure.Figure):
54
+ is_pyplot = True
55
+ except ImportError: pass
56
+
57
+ if is_pyplot:
58
+ flush_buffer()
59
+ if hasattr(self, 'pyplot'):
60
+ self.pyplot(arg)
61
+ continue
73
62
 
74
- # Join all parts
75
- content = " ".join(parts)
63
+ # 3. Plotly Figure
64
+ is_plotly = False
65
+ try:
66
+ if hasattr(check_val, 'to_plotly_json'):
67
+ is_plotly = True
68
+ except ImportError: pass
69
+
70
+ if is_plotly:
71
+ flush_buffer()
72
+ if hasattr(self, 'plotly_chart'):
73
+ self.plotly_chart(arg)
74
+ continue
75
+
76
+ # 4. Dict / List (JSON)
77
+ if isinstance(check_val, (dict, list)):
78
+ flush_buffer()
79
+ if hasattr(self, 'json'):
80
+ self.json(arg)
81
+ else:
82
+ # Fallback if json widget logic is missing for State
83
+ # But wait, we need to fix json widget too.
84
+ self.json(arg)
85
+ continue
76
86
 
77
- # Check if any HTML in content
78
- has_html = '<' in content and '>' in content
79
- return Component(tag, id=cid, content=content, escape_content=not (has_html or unsafe_allow_html), **props)
87
+ # 5. Exception
88
+ if isinstance(check_val, Exception):
89
+ flush_buffer()
90
+ if hasattr(self, 'exception'):
91
+ self.exception(arg)
92
+ else:
93
+ self.error(str(arg))
94
+ continue
80
95
 
81
- finally:
82
- rendering_ctx.reset(token)
83
-
84
- self._register_component(cid, builder)
96
+ # Default: Text-like (str, int, float, State, ComputedState)
97
+ text_buffer.append(arg)
98
+
99
+ # Flush remaining text
100
+ flush_buffer()
85
101
 
86
102
  def _render_markdown(self, text: str) -> str:
87
103
  """Render markdown to HTML (internal helper)"""
@@ -191,20 +207,25 @@ class TextWidgetsMixin:
191
207
  '''
192
208
  return styled_html
193
209
 
194
- def heading(self, text, level: int = 1, divider: bool = False):
210
+ def heading(self, *args, level: int = 1, divider: bool = False):
195
211
  """Display heading (h1-h6)"""
196
- from ..state import State
212
+ from ..state import State, ComputedState
197
213
  import html as html_lib
198
214
 
199
215
  cid = self._get_next_cid("heading")
200
216
  def builder():
201
217
  token = rendering_ctx.set(cid)
202
- if isinstance(text, State):
203
- content = text.value
204
- elif callable(text):
205
- content = text()
206
- else:
207
- content = text
218
+
219
+ parts = []
220
+ for arg in args:
221
+ if isinstance(arg, (State, ComputedState)):
222
+ parts.append(str(arg.value))
223
+ elif callable(arg):
224
+ parts.append(str(arg()))
225
+ else:
226
+ parts.append(str(arg))
227
+
228
+ content = " ".join(parts)
208
229
  rendering_ctx.reset(token)
209
230
 
210
231
  # XSS protection: escape content
@@ -216,48 +237,71 @@ class TextWidgetsMixin:
216
237
  return Component("div", id=cid, content=html_output)
217
238
  self._register_component(cid, builder)
218
239
 
219
- def title(self, text: Union[str, Callable]):
240
+ def title(self, *args):
220
241
  """Display title (h1 with gradient)"""
221
- self.heading(text, level=1, divider=False)
242
+ self.heading(*args, level=1, divider=False)
222
243
 
223
- def header(self, text: Union[str, Callable], divider: bool = True):
244
+ def header(self, *args, divider: bool = True):
224
245
  """Display header (h2)"""
225
- self.heading(text, level=2, divider=divider)
246
+ self.heading(*args, level=2, divider=divider)
226
247
 
227
- def subheader(self, text: Union[str, Callable], divider: bool = False):
248
+ def subheader(self, *args, divider: bool = False):
228
249
  """Display subheader (h3)"""
229
- self.heading(text, level=3, divider=divider)
250
+ self.heading(*args, level=3, divider=divider)
230
251
 
231
- def text(self, content, size: str = "medium", muted: bool = False):
232
- """Display text paragraph"""
233
- from ..state import State
252
+ def text(self, *args, size: str = "medium", muted: bool = False):
253
+ """Display text paragraph
254
+
255
+ Supports multiple arguments which will be joined by spaces.
256
+ """
257
+ from ..state import State, ComputedState
234
258
 
235
259
  cid = self._get_next_cid("text")
236
260
  def builder():
237
261
  token = rendering_ctx.set(cid)
238
- if isinstance(content, State):
239
- val = content.value
240
- elif callable(content):
241
- val = content()
242
- else:
243
- val = content
262
+
263
+ parts = []
264
+ for arg in args:
265
+ if isinstance(arg, (State, ComputedState)):
266
+ parts.append(str(arg.value))
267
+ elif callable(arg):
268
+ parts.append(str(arg()))
269
+ else:
270
+ parts.append(str(arg))
271
+
272
+ val = " ".join(parts)
244
273
  rendering_ctx.reset(token)
274
+
245
275
  cls = f"text-{size} {'text-muted' if muted else ''}"
246
276
  # XSS protection: enable content escaping
247
277
  return Component("p", id=cid, content=val, escape_content=True, class_=cls)
248
278
  self._register_component(cid, builder)
249
279
 
250
- def caption(self, text: Union[str, Callable]):
280
+ def caption(self, *args):
251
281
  """Display caption text (small, muted)"""
252
- self.text(text, size="small", muted=True)
282
+ self.text(*args, size="small", muted=True)
253
283
 
254
- def markdown(self, text: Union[str, Callable], **props):
255
- """Display markdown-formatted text"""
284
+ def markdown(self, *args, **props):
285
+ """Display markdown-formatted text
286
+
287
+ Supports multiple arguments which will be joined by spaces.
288
+ """
256
289
  cid = self._get_next_cid("markdown")
257
290
  def builder():
258
291
  token = rendering_ctx.set(cid)
259
- content = text() if callable(text) else text
292
+ from ..state import State, ComputedState
293
+
294
+ parts = []
295
+ for arg in args:
296
+ if isinstance(arg, (State, ComputedState)):
297
+ parts.append(str(arg.value))
298
+ elif callable(arg):
299
+ parts.append(str(arg()))
300
+ else:
301
+ parts.append(str(arg))
260
302
 
303
+ content = " ".join(parts)
304
+
261
305
  # Enhanced markdown conversion - line-by-line processing
262
306
  import re
263
307
  lines = content.split('\n')
@@ -332,19 +376,30 @@ class TextWidgetsMixin:
332
376
  return Component("div", id=cid, content=html, class_="markdown", **props)
333
377
  self._register_component(cid, builder)
334
378
 
335
- def html(self, html_content: Union[str, Callable], **props):
379
+ def html(self, *args, **props):
336
380
  """Display raw HTML content
337
381
 
338
382
  Use this when you need to render HTML directly without markdown processing.
339
383
  For markdown formatting, use app.markdown() instead.
340
384
 
341
385
  Example:
342
- app.html('<div class="custom">Hello</div>')
386
+ app.html('<div class="custom">', count, '</div>')
343
387
  """
344
388
  cid = self._get_next_cid("html")
345
389
  def builder():
390
+ from ..state import State, ComputedState
346
391
  token = rendering_ctx.set(cid)
347
- content = html_content() if callable(html_content) else html_content
392
+
393
+ parts = []
394
+ for arg in args:
395
+ if isinstance(arg, (State, ComputedState)):
396
+ parts.append(str(arg.value))
397
+ elif callable(arg):
398
+ parts.append(str(arg()))
399
+ else:
400
+ parts.append(str(arg))
401
+
402
+ content = " ".join(parts)
348
403
  rendering_ctx.reset(token)
349
404
  return Component("div", id=cid, content=content, **props)
350
405
  self._register_component(cid, builder)
@@ -371,16 +426,6 @@ class TextWidgetsMixin:
371
426
  return Component("div", id=cid, content=html_output, **props)
372
427
  self._register_component(cid, builder)
373
428
 
374
- def html(self, html_content: Union[str, Callable], **props):
375
- """Render raw HTML"""
376
- cid = self._get_next_cid("html")
377
- def builder():
378
- token = rendering_ctx.set(cid)
379
- content = html_content() if callable(html_content) else html_content
380
- rendering_ctx.reset(token)
381
- return Component("div", id=cid, content=content, **props)
382
- self._register_component(cid, builder)
383
-
384
429
  def divider(self):
385
430
  """Display horizontal divider"""
386
431
  cid = self._get_next_cid("divider")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: violit
3
- Version: 0.0.5
3
+ Version: 0.0.6
4
4
  Summary: Violit: Faster than Light, Beautiful as Violet. The High-Performance Python Web Framework (Streamlit Alternative with Zero Rerun).
5
5
  Author-email: Violit Team <violit.company@gmail.com>
6
6
  License: MIT
@@ -2,25 +2,25 @@ violit/__init__.py,sha256=mD8FeVW-LQ8yLpW2Unc-K885T507BgRfSI7mmObpPK0,88
2
2
  violit/app.py,sha256=G_li1AU8vH25T1VryG6kb7jrYV_zUucBdbg_goA_Q98,102272
3
3
  violit/broadcast.py,sha256=IIRQR15Wr7cqHh0nSH86QdWN_Wd_GHNfVrtWCATGElc,24293
4
4
  violit/broadcast_primitives.py,sha256=G4Nx9BZscidPAjUc9janwIJ0zUi6DAnNFeJS67Uvs9I,5739
5
- violit/component.py,sha256=zH-ln8CVrkqfgB-_SPUmCL8dqQv-8RbKb2p9gtYqlqI,1445
5
+ violit/component.py,sha256=HBh72unqjNUri05UAK_TnQVsPJULot2hgAQK8HKqQLM,1483
6
6
  violit/context.py,sha256=oai3FiBFwZbAK-ONHCPzItITHjXeZ3zXNya_BmPlX8I,517
7
7
  violit/engine.py,sha256=oH3C04vhVgZFgy0HSaI9vPOkBEZBQBz40zs1Y1my9vw,1829
8
- violit/state.py,sha256=KcsgqYvn7yTr8RGxxXZNzDPwCTmW9CE4P2EEx6DVfNY,3712
8
+ violit/state.py,sha256=oxIVCa-nencMjQXAs9ZMwfeTuz4w0LAeKDvBv_lvzhQ,12721
9
9
  violit/theme.py,sha256=1Ht2DariRADxGU1GTtUFJaIXZXi9XuhnucT2d04cwhs,30781
10
10
  violit/widgets/__init__.py,sha256=DdNdPahOIxEGKnV-MP-amA3jKGnE8GCUxKrtKN2sR9o,877
11
11
  violit/widgets/card_widgets.py,sha256=rRGuY-7pArbneILQ8cLkBgiE820S6RCe1a2EZq0ZA3k,23112
12
- violit/widgets/chart_widgets.py,sha256=swFvpfQ4A6ObXcBs90Pe5pIlIUzsZ4x97veWsMR4UFs,10428
12
+ violit/widgets/chart_widgets.py,sha256=WtPRunw_2t45RTPtIHuvEkBd8miTJUL6HOYlVlvHE9k,10681
13
13
  violit/widgets/chat_widgets.py,sha256=YHNY4SDqHJhTi3Lq_5lGl01y9jsJAfbPlH4Ar0UqYC8,10899
14
- violit/widgets/data_widgets.py,sha256=KUhgq5_wRUGciFJX9uxHsX6w1e9Gj40__azC9Kn6gg8,22712
14
+ violit/widgets/data_widgets.py,sha256=NhQnQ_FAD2ey4mXUxaPT67rzjen-BLKU7pZSPmcln5Y,23431
15
15
  violit/widgets/form_widgets.py,sha256=QVj-MRVpYzIM3hE8ISS5xZcX3jlTTzjRYSdzhw-K5OY,18327
16
- violit/widgets/input_widgets.py,sha256=tiLpfaKBwGhE2CdOL9s950bLVz2WhMP4Y-1yRM0qIAY,32023
16
+ violit/widgets/input_widgets.py,sha256=FjuSzNg00PnoQFyQDt6b6pWWrqofVcQeHHJX4L42voU,32768
17
17
  violit/widgets/layout_widgets.py,sha256=--2vxZAxVfPCrLz74OBE5RM4x1RoOfMR3w1w79kXUFg,17606
18
18
  violit/widgets/list_widgets.py,sha256=2-FWluNGfdBBN24ZyHOC2a8O93-NzGYHZlVYARX4FpI,3836
19
19
  violit/widgets/media_widgets.py,sha256=FIMjaE1CIfqnsdMHbbCromjwxoWPfvFKmL0_qd6nDL4,7386
20
- violit/widgets/status_widgets.py,sha256=HXfS2F_PhEnwHrKNEMFv9JTgmeXgpfspoqq_jJZRPEQ,11140
21
- violit/widgets/text_widgets.py,sha256=r2QOsi5qOPMEHhVXNBd2g_U_f5-en_4xHBh1udenCSA,17422
22
- violit-0.0.5.dist-info/licenses/LICENSE,sha256=dMxDVLF-GU5ZhQ-GmpL_eY4j7Wn1c4CtmRU8cOnR7Hw,1093
23
- violit-0.0.5.dist-info/METADATA,sha256=ZdOykMhsRYHrtJgLPt3mb-yAxnatabXPWfM0crvX9cU,12599
24
- violit-0.0.5.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
25
- violit-0.0.5.dist-info/top_level.txt,sha256=VormCSpHvTyMyZQ17MwgiPegDJev8HQCN5HUUcxUcE0,7
26
- violit-0.0.5.dist-info/RECORD,,
20
+ violit/widgets/status_widgets.py,sha256=ImA8vunM2Vk30-eECyl0eGiN75Nj7qtciwHgwgrymAs,13447
21
+ violit/widgets/text_widgets.py,sha256=yFlcDrJYTQTB25X562xiVZWS0Lz3-tljFxJcGRtAlb4,17867
22
+ violit-0.0.6.dist-info/licenses/LICENSE,sha256=dMxDVLF-GU5ZhQ-GmpL_eY4j7Wn1c4CtmRU8cOnR7Hw,1093
23
+ violit-0.0.6.dist-info/METADATA,sha256=nh6AXUz9720pCMLFxxYNjwITb2rD5_4PDQGxHXtAY2o,12599
24
+ violit-0.0.6.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
25
+ violit-0.0.6.dist-info/top_level.txt,sha256=VormCSpHvTyMyZQ17MwgiPegDJev8HQCN5HUUcxUcE0,7
26
+ violit-0.0.6.dist-info/RECORD,,
File without changes